Properties
Notion-style page properties under the title, typed by a schema, stored in the document's data, which users may add or hide.
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.
<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:
<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).