---
title: Properties
description: Notion-style page properties under the title, typed by a schema, stored in the document's data, which users may add or hide.
icon: table-properties
---

`properties` is the page's schema: one row per property under the title, each with its name and a cell of its type. The values live in the document's data (`data.properties`, by id), so they sync and undo like the rest of the page.

```svelte title="src/lib/TaskPage.svelte" check
<script lang="ts">
	import {
		StarterEditor,
		type StarterProperty
	} from '$lib/components/edytor/starter/index.js';

	const people = [
		{ id: 'ada', label: 'Ada Lovelace' },
		{ id: 'alan', label: 'Alan Turing' }
	];
	const properties: StarterProperty[] = [
		{
			id: 'status',
			name: 'Status',
			type: 'select',
			options: [
				{ value: 'todo', label: 'To do', color: 'gray' },
				{ value: 'doing', label: 'Doing', color: 'blue' },
				{ value: 'done', label: 'Done', color: 'green' }
			]
		},
		{ id: 'owner', name: 'Owner', type: 'person' },
		{ id: 'due', name: 'Due', type: 'date' },
		{ id: 'estimate', name: 'Estimate', type: 'number', hidden: true }
	];
</script>

<StarterEditor {properties} {people} />
```

## Types

| `type` | Stored as | The cell |
| --- | --- | --- |
| `text` | a string | an `Input` |
| `number` | a number | a number `Input` |
| `select` | one option's `value` | a `Select` of the options, the chosen one a badge in its colour |
| `multi-select` | a list of option values | a badge per value with its remove button, and a Combobox (a `Popover` over a searchable `Command`) whose rows toggle a value |
| `date` | ISO `YYYY-MM-DD` | the Date Picker (a `Popover` over a `Calendar`), the date shown in `locale` (`Oct 13, 2026`) |
| `checkbox` | `true` (unchecked: absent) | a `Checkbox` |
| `url` | a string | a URL `Input`, and a button to open it |
| `person` | a list of person ids | a badge per person from `people`, and the same Combobox over them |

An empty value (`''`, an empty list, an unchecked box) removes the property's key, so `data.properties` holds only what is set.

## Dates

A `date` is stored as an ISO date, `2026-10-13`, whatever the user's language, so it sorts, compares and syncs the same everywhere. The page shows it in the `locale` prop (default `en-US`), medium style, through `Intl.DateTimeFormat` in UTC: `Oct 13, 2026`, `13 oct. 2026` in `fr-FR`. The locale and the time zone are given, never the runtime's, so the server's text and the browser's are the same and the page hydrates without a mismatch; pass your user's locale from the server (`locale={data.locale}`) to show their format. The Date Picker's calendar uses the same locale; **Clear** removes the date.

## A property

| Field | Default | What it does |
| --- | --- | --- |
| `id` | | Its key in `data.properties`. |
| `type` | | One of the types above. |
| `name` | `id` | What the page shows; each cell is labelled by it. |
| `icon` | none | A short icon before the name. |
| `options` | `[]` | A `select` or `multi-select` property's options: `{ value, label?, color? }`, `color` a block colour name (`gray`, `brown`, `orange`, `yellow`, `green`, `blue`, `purple`, `pink`, `red`). |
| `hidden` | `false` | Shown only once a user adds it. |
| `removable` | `true` | Users may hide it. |
| `placeholder` | `Empty` | The empty cell's text. |

## Add and hide

With `editableProperties` (the default), each shown property has a hide button, and **Add a property** is a searchable list of the hidden ones. Hiding a property keeps its value: the hidden properties sit under a collapsible below the shown ones (**2 hidden properties**), each with its value and a show button, so nothing is lost and a user can bring one back. A user's choice is the document's (`data.shownProperties`, `true` for a shown one, `false` for a hidden one), the values stay in `data.properties`. `editableProperties={false}` shows the schema as it is, without the hide, show and add controls. A readonly view shows the values only, the hidden ones under the same collapsible.

## Replace a cell

`propertyCells` replaces the cell of every property of a type; the snippet gets the property, its value, a `set` (one undo step), `readonly`, `people`, the `locale` and the `labelId` its field is labelled by:

```svelte title="src/lib/Stars.svelte" check
<script lang="ts">
	import {
		StarterEditor,
		type StarterPropertyCellPayload
	} from '$lib/components/edytor/starter/index.js';
</script>

{#snippet stars({ value, set, readonly, labelId }: StarterPropertyCellPayload)}
	<input
		type="range"
		min="0"
		max="5"
		value={Number(value ?? 0)}
		disabled={readonly}
		aria-labelledby={labelId}
		oninput={(event) => set(Number(event.currentTarget.value))}
	/>
{/snippet}

<StarterEditor
	properties={[{ id: 'rating', name: 'Rating', type: 'number' }]}
	propertyCells={{ number: stars }}
/>
```

`propertyList` replaces the whole block: it gets the shown properties, the hidden ones, the ones a user may add, the values, `set`, `show`, `hide`, `readonly`, `editable`, `labelId` and `cell`, the snippet of a property's cell (yours where `propertyCells` has one).
