@jungherz-de/notionkit-elements 1.10.1 → 1.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/CHANGELOG.md +641 -0
  2. package/README.md +6 -6
  3. package/SKILL.md +3748 -0
  4. package/dist/components/nk-ai-msg.js +6 -4
  5. package/dist/components/nk-avatar.js +1 -1
  6. package/dist/components/nk-banner.js +5 -2
  7. package/dist/components/nk-board-view.js +2 -2
  8. package/dist/components/nk-calendar.js +2 -2
  9. package/dist/components/nk-comment.js +1 -1
  10. package/dist/components/nk-dialog.js +14 -8
  11. package/dist/components/nk-fields.js +13 -3
  12. package/dist/components/nk-filter-bar.js +5 -3
  13. package/dist/components/nk-list-view.js +2 -2
  14. package/dist/components/nk-member-row.js +1 -1
  15. package/dist/components/nk-menu.js +2 -2
  16. package/dist/components/nk-page.js +3 -2
  17. package/dist/components/nk-select.js +15 -7
  18. package/dist/components/nk-switch.js +5 -1
  19. package/dist/components/nk-table-view.js +2 -2
  20. package/dist/components/nk-tooltip.js +17 -7
  21. package/dist/components/nk-tree-item.js +10 -4
  22. package/dist/components/shared/{avatar-BiqCaHOt.js → avatar-CzPZN3uP.js} +2 -2
  23. package/dist/components/shared/floating-CtqV6MBj.js +66 -0
  24. package/dist/components/shared/{property-cell-o7N30kAQ.js → property-cell-Cddz5n2O.js} +1 -1
  25. package/dist/notionkit-elements.esm.js +137 -56
  26. package/dist/notionkit-elements.js +137 -56
  27. package/dist/notionkit-elements.min.js +1 -1
  28. package/package.json +7 -4
  29. package/src/components/data/nk-ai-msg.js +6 -4
  30. package/src/components/data/nk-filter-bar.js +5 -3
  31. package/src/components/forms/nk-calendar.js +1 -1
  32. package/src/components/forms/nk-fields.js +13 -3
  33. package/src/components/forms/nk-select.js +15 -7
  34. package/src/components/forms/nk-switch.js +5 -1
  35. package/src/components/overlays/nk-dialog.js +14 -8
  36. package/src/components/overlays/nk-menu.js +1 -1
  37. package/src/components/overlays/nk-tooltip.js +16 -6
  38. package/src/components/page/nk-banner.js +5 -2
  39. package/src/components/page/nk-page.js +3 -2
  40. package/src/components/shell/nk-tree-item.js +10 -4
  41. package/src/util/avatar.js +2 -2
  42. package/src/util/floating.js +39 -10
  43. package/dist/components/shared/floating-CxHxy-Cb.js +0 -37
package/SKILL.md ADDED
@@ -0,0 +1,3748 @@
1
+ ---
2
+ name: notionkit-elements
3
+ description: NotionKit Elements is a vanilla-JS Web Components library (v1.11.1) wrapping NotionKit CSS v1.11.1 – the calm, document-centric design system in the Notion idiom. 87 custom elements with the `nk-` prefix, Shadow DOM, automatic light/dark sync via data-theme on <html>, and form-associated controls. Use this reference whenever generating HTML that uses <nk-*> tags to get attributes, slots, events and composition right.
4
+ ---
5
+
6
+ # NotionKit Elements – AI Component Reference
7
+
8
+ > Machine-readable reference for generating correct `<nk-*>` markup. The class-based companion (`.nk-*`) is documented in the NotionKit CSS SKILL.md at https://notionkit.jungherz.com/SKILL.md – every element here has the same word stem as its class.
9
+
10
+ # 1. Setup & Boilerplate
11
+
12
+ ## Prerequisites (always)
13
+
14
+ 1. Load **notionkit.css** on the document (it is the peer dependency) and put `class="nk-body"` on `<body>`. Shadow roots inherit font, colour and the scoped reset from there; the elements ship no visual CSS of their own.
15
+ 2. Load the elements bundle **once**. It also injects the design tokens as a cascade layer (`@layer notionkit-defaults`), so your own unlayered `:root { --nk-* }` always wins.
16
+ 3. Theme: `data-theme="light|dark"` on `<html>` only.
17
+
18
+ ```html
19
+ <!DOCTYPE html>
20
+ <html lang="en" data-theme="light">
21
+ <head>
22
+ <meta charset="UTF-8">
23
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
24
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit@1.11.1/notionkit.min.css">
25
+ <script src="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit-elements@1.11.1/dist/notionkit-elements.min.js"></script>
26
+ </head>
27
+ <body class="nk-body">
28
+ <nk-btn variant="primary">Save</nk-btn>
29
+ </body>
30
+ </html>
31
+ ```
32
+
33
+ ## npm
34
+
35
+ ```bash
36
+ npm install @jungherz-de/notionkit-elements @jungherz-de/notionkit
37
+ ```
38
+
39
+ ```js
40
+ import '@jungherz-de/notionkit/notionkit.css'; // via your bundler, or a <link>
41
+ import '@jungherz-de/notionkit-elements'; // registers every <nk-*> tag
42
+ // or one element at a time (shared code: base.js):
43
+ import '@jungherz-de/notionkit-elements/components/nk-btn.js';
44
+ // the sheet the elements adopt, for your own views – never a second copy:
45
+ import { componentsSheet } from '@jungherz-de/notionkit-elements';
46
+ ```
47
+
48
+ The per-component files import NotionKit's sheet as `@jungherz-de/notionkit/notionkit-styles.js` instead of carrying a copy. A bundler resolves it; a build-free page maps it once:
49
+
50
+ ```html
51
+ <script type="importmap">{ "imports": { "@jungherz-de/notionkit/notionkit-styles.js": "/node_modules/@jungherz-de/notionkit/notionkit-styles.js" } }</script>
52
+ ```
53
+
54
+ Never mix the full bundle with the per-component files – each brings its own `NkElement`. With the bundle, take `componentsSheet` and `NkElement` from it (`NotionKitElements.componentsSheet` from the `<script>` build).
55
+
56
+
57
+ # 2. Core Concepts
58
+
59
+ | Concept | Rule |
60
+ |---|---|
61
+ | Tag prefix | Every element is `<nk-*>`; the word stem equals the CSS class (`.nk-callout` ↔ `<nk-callout>`). |
62
+ | Modifiers | A modifier class becomes an attribute: `.nk-btn.primary` → `<nk-btn variant="primary">`, `.nk-tag.green` → `<nk-tag color="green">`. |
63
+ | States | A state class becomes a boolean attribute: `.active`, `.open`, `.selected`, `checked`. Set the attribute (or property) – never reach into the shadow root. |
64
+ | Rendering | Open Shadow DOM. The shadow root adopts the NotionKit *component* sheet only; tokens are inherited from the document. |
65
+ | Hosts | Every host is `display: contents` – no box of its own, the inner `.nk-*` element sits in the parent layout exactly like the class markup. Style the parent or the tokens, never the host; `hidden` on the host works. |
66
+ | Theme | One MutationObserver watches `data-theme` on `<html>` and mirrors it into every element. Nothing else switches themes. |
67
+ | Branding | Declare `--nk-*` tokens on `:root` in any plain stylesheet; every element follows in both themes. |
68
+ | Data | Static content via attributes and slots; dynamic data via JS properties (`tree.data`, `database.rows`, `cmdk.commands`). No fetching, no two-way binding. |
69
+ | Events | Custom events with fixed names (`nk-select`, `nk-change`, `nk-view-change`, `nk-command`, `nk-toggle`, `nk-submit`, `nk-action`). All bubble and are composed; payload in `event.detail`. |
70
+ | Forms | Controls are form-associated: FormData, reset, `required`, `<fieldset disabled>` work inside a `<form>`. |
71
+ | Icons | `::slotted()` only matches the assigned node. Pass an icon as the slotted node itself – `<span slot="icon">📁</span>` – never wrapped. |
72
+ | Light-DOM children | Elements that copy children (`nk-select` options, breadcrumb crumbs) watch them; `element.refresh()` is the escape hatch. The empty string is a valid value. |
73
+ | Moving elements | An element moved in the DOM keeps working – listeners and theme registration are re-armed on every connect. |
74
+ | Attributes are live | Every documented attribute re-renders when changed after connect (`stat.setAttribute('value', '129')`, `el.open = true`); properties reflect to attributes where a setter is listed. |
75
+
76
+
77
+ # 3. Element Catalog (87 elements)
78
+
79
+ ## Forms & controls (wave 1)
80
+
81
+ ### 3.1 `<nk-btn>` – Button
82
+
83
+ Renders `button.nk-btn`, or `a.nk-btn` when `href` is set. Modifier classes become attributes. A slotted `<svg>` is sized by the stylesheet – pass it directly, never wrapped.
84
+
85
+ ```html
86
+ <nk-btn variant="primary">Save</nk-btn>
87
+ <nk-btn variant="secondary">Cancel</nk-btn>
88
+ <nk-btn variant="danger" small>Delete</nk-btn>
89
+ ```
90
+
91
+ | Attribute | Type | Default | Description |
92
+ |---|---|---|---|
93
+ | `variant` | primary | secondary | danger | danger-solid | topbar | share | tool | sidebar | – | Visual variant. `topbar` and `share` render `.nk-topbar-btn` for the top bar. `tool` renders `.nk-db-tool`, a tool in the database toolbar (`slot="tools"` of `nk-database`). `sidebar` is the ☰ `.nk-topbar-btn.nk-sidebar-toggle`: shown below 860px only, a click opens the `nk-sidebar` of its app as a drawer and sets `aria-expanded`. |
94
+ | `active` | boolean | – | A tool in effect – a set filter – takes the accent (`variant="tool"`). |
95
+ | `small` | boolean | – | Compact padding and 12.5px text. |
96
+ | `disabled` | boolean | – | Disabled; clicks are swallowed. |
97
+ | `type` | button | submit | reset | `button` | For `submit`/`reset` the surrounding `<form>` is submitted or reset. |
98
+ | `href` | URL | – | Renders a link instead of a button. |
99
+
100
+ **Slots:** `(default)` – Label text and an optional `<svg>` icon.
101
+
102
+ **Events:** `click` `(native, composed)` – The native click bubbles out of the shadow root.
103
+
104
+ **Replaces:** `.nk-btn`, `.primary`, `.secondary`, `.danger`, `.danger-solid`, `.small`, `.nk-topbar-btn`, `.nk-share-btn`, `.nk-db-tool`, `.active`, `.nk-sidebar-toggle`
105
+
106
+ ```html
107
+ <!-- equivalent class markup -->
108
+ <button class="nk-btn primary">Save</button>
109
+ <button class="nk-btn secondary">Cancel</button>
110
+ <button class="nk-btn danger small">Delete</button>
111
+ ```
112
+
113
+ **Small screens:** Unchanged. The button grows with its label; combine with `small` in dense toolbars.
114
+
115
+ ### 3.2 `<nk-input>` – Input
116
+
117
+ A native `<input>` inside the shadow root, wired into the surrounding form through ElementInternals: FormData, reset and `required` validation work as with a plain input.
118
+
119
+ ```html
120
+ <nk-input name="name" value="Ada Lovelace" placeholder="Display name"></nk-input>
121
+ ```
122
+
123
+ | Attribute | Type | Default | Description |
124
+ |---|---|---|---|
125
+ | `value` | string | – | Current value; also the reset value. |
126
+ | `type` | text | email | password | number | date | … | `text` | Forwarded to the native input. |
127
+ | `placeholder` | string | – | Placeholder text. |
128
+ | `name` | string | – | Form field name (FormData key). |
129
+ | `disabled` | boolean | – | Disables the control. |
130
+ | `required` | boolean | – | Marks the field required; validity is mirrored onto the host. |
131
+ | `readonly` | boolean | – | Read-only. |
132
+ | `wide` | boolean | – | Full width (`.wide`). |
133
+
134
+ **Events:** `nk-change` `{ value, name }` – Fired on commit (blur/Enter), like the native change event. · `nk-input` `{ value, name }` – Fired on every keystroke.
135
+
136
+ **Properties:** `value`, `name`, `disabled`, `required`, `form`, `validity` · **Methods:** `focus()`, `blur()`, `select()`, `checkValidity()`, `reportValidity()`
137
+
138
+ **Replaces:** `.nk-input`, `.wide`
139
+
140
+ ```html
141
+ <!-- equivalent class markup -->
142
+ <input class="nk-input" name="name" value="Ada Lovelace" placeholder="Display name">
143
+ ```
144
+
145
+ **Small screens:** Minimum width 210px; use `wide` to fill the row.
146
+
147
+ ### 3.3 `<nk-copy-field>` – Copy field
148
+
149
+ A value to take along – a link, an address, a key – as tall as an input, with Copy inside on the right: it writes the clipboard and says “Copied” in green for a moment; where the clipboard is not allowed, the value is shown and selected for ⌘C. `secret` masks it behind Show/Hide, Copy still copies the real value. `value` set as a property is not reflected, so a key never lands in the markup.
150
+
151
+ ```html
152
+ <div style="max-width:340px"><nk-copy-field value="https://monahilft.notionkit.app" copy-label="Copy"></nk-copy-field></div>
153
+ <div style="max-width:340px;margin-top:10px"><nk-copy-field value="ntn_4f2a9c1e8b7d6a5f3e2c" secret mono copy-label="Copy" show-label="Show"></nk-copy-field></div>
154
+ ```
155
+
156
+ | Attribute | Type | Default | Description |
157
+ |---|---|---|---|
158
+ | `value` | string | – | The value. |
159
+ | `secret` | boolean | – | Masked, with Show/Hide. |
160
+ | `mono` | boolean | – | Monospace – addresses, keys, code. |
161
+ | `wrap` | boolean | – | A long value breaks instead of an ellipsis. |
162
+ | `wide` | boolean | – | Fills the row. |
163
+ | `copy-label` | string | `Copy` | Button text. |
164
+ | `copied-label` | string | `Copied` | Text for the moment after. |
165
+ | `show-label` | string | `Show` | Reveal (secret). |
166
+ | `hide-label` | string | `Hide` | Mask again. |
167
+
168
+ **Events:** `nk-action` `{ action: 'copy', value, ok }` – Copy clicked; `ok` is false where the clipboard refused.
169
+
170
+ **Properties:** `value`, `secret` · **Methods:** `copy()`
171
+
172
+ **Replaces:** `.nk-copy-field`, `.cf-value`, `.cf-btn`, `.copied`, `.mono`, `.wrap`, `.wide`
173
+
174
+ ```html
175
+ <!-- equivalent class markup -->
176
+ <div style="max-width:340px"><div class="nk-copy-field"><span class="cf-value">https://monahilft.notionkit.app</span><button class="cf-btn">Copy</button></div></div>
177
+ <div style="max-width:340px;margin-top:10px"><div class="nk-copy-field mono"><span class="cf-value">••••••••••••••••••••••••</span><button class="cf-btn">Show</button><button class="cf-btn">Copy</button></div></div>
178
+ ```
179
+
180
+ **Small screens:** Keeps to its column: the value is cut, never the actions.
181
+
182
+ ### 3.4 `<nk-image-picker>` – Image picker
183
+
184
+ A picture for a person or a workspace, on NotionKit’s profile row: round, or `square` for a workspace icon, with Upload / Change and Remove beside it. From GlassKit Elements: the file is decoded with its EXIF rotation, drawn no larger than `max` and handed over as a data URL in `nk-change` – upload it yourself; an unreadable file fires `nk-error`. JPEG is drawn on white; `type="image/png"` keeps transparency. `src` as a property is not reflected, so megabytes never land in the DOM.
185
+
186
+ ```html
187
+ <nk-image-picker initials="AL" choose-label="Upload image"></nk-image-picker>
188
+ <nk-image-picker square src="/favicon.svg" change-label="Change image" remove-label="Remove"></nk-image-picker>
189
+ ```
190
+
191
+ | Attribute | Type | Default | Description |
192
+ |---|---|---|---|
193
+ | `src` | URL / data URL | – | Starting picture. |
194
+ | `initials` | string | – | Shown without a picture. |
195
+ | `square` | boolean | – | Rounded square – a workspace icon. |
196
+ | `max` | px | `512` | Longest edge after scaling. |
197
+ | `type` | MIME | `image/jpeg` | Output format. |
198
+ | `quality` | 0–1 | `0.82` | JPEG / WebP quality. |
199
+ | `accept` | string | `image/*` | File dialog filter. |
200
+ | `label` | string | – | Names the group. |
201
+ | `choose-label` | string | `Upload image` | Without a picture. |
202
+ | `change-label` | string | `Change image` | With a picture. |
203
+ | `remove-label` | string | `Remove` | Remove button. |
204
+
205
+ **Events:** `nk-change` `{ dataUrl, width, height, size }` – A picture chosen – or removed, with an empty dataUrl. · `nk-error` `{ message, name }` – The file cannot be decoded.
206
+
207
+ **Properties:** `src`, `square` · **Methods:** `choose()`
208
+
209
+ **Replaces:** `.nk-profile-row`, `.big-avatar`, `.square`, `.pr-actions`, `.pr-remove`
210
+
211
+ ```html
212
+ <!-- equivalent class markup -->
213
+ <div class="nk-profile-row"><div class="big-avatar">AL</div><div class="pr-actions"><button class="nk-btn secondary small">Upload image</button></div></div>
214
+ <div class="nk-profile-row"><div class="big-avatar square"><img src="/favicon.svg" alt=""></div><div class="pr-actions"><button class="nk-btn secondary small">Change image</button><button class="nk-btn secondary small pr-remove">Remove</button></div></div>
215
+ ```
216
+
217
+ **Small screens:** Unchanged; the actions stay beside the picture.
218
+
219
+ ### 3.5 `<nk-calendar>` – Date picker
220
+
221
+ Notion’s date picker as one element (NotionKit 1.9.0): a month with Today and ‹ ›, the keys, bounds and a form value. The value is a day (`YYYY-MM-DD`); with `range` an interval `start/end`, picked in two clicks in either order; with `time` a day and a time (`YYYY-MM-DDTHH:MM`). `weeks` puts the ISO calendar week in front of each row (“KW” in German), `weekend` greys the days not worked, the `days` property adds holidays (`off` with a `label`) and up to three `marks` per day in the nine colours – deadlines, milestones. Days outside `min`/`max` are announced as unavailable and cannot be picked. Arrows move by a day or a week, Home/End to the ends of the week, PageUp/PageDown by a month (with Shift a year), Enter or Space picks; one day is in the tab order. Month names, weekdays and the first day of the week come from the page’s language; `week-start="1"` fixes Monday. With `floating sheet` it is a popover that `show(anchor)` opens under a property or a cell, and a bottom sheet on a phone; a pick closes it.
222
+
223
+ ```html
224
+ <div style="display:flex;gap:16px;flex-wrap:wrap;align-items:flex-start">
225
+ <div class="nk-pop"><nk-calendar name="due" value="2026-06-02T09:30" time clearable weeks week-start="1" weekend="6,0" today="2026-06-17" today-label="Today" time-label="Time" clear-label="Clear" days='{"2026-06-04":{"off":true,"label":"Corpus Christi"},"2026-06-02":{"marks":["blue"]},"2026-06-11":{"marks":["orange","red"]},"2026-06-24":{"marks":["green"]}}'></nk-calendar></div>
226
+ <div class="nk-pop"><nk-calendar name="sprint" range value="2026-06-08/2026-06-12" weeks week-start="1" weekend="6,0" today="2026-06-17" today-label="Today" days='{"2026-06-04":{"off":true,"label":"Corpus Christi"}}'></nk-calendar></div>
227
+ </div>
228
+ ```
229
+
230
+ | Attribute | Type | Default | Description |
231
+ |---|---|---|---|
232
+ | `value` | YYYY-MM-DD | start/end | …THH:MM | – | The day, the range or the day and time; also the reset value. |
233
+ | `month` | YYYY-MM | – | The month shown first; default the value’s, else today’s. |
234
+ | `min` | YYYY-MM-DD | – | First day that can be picked. |
235
+ | `max` | YYYY-MM-DD | – | Last day that can be picked. |
236
+ | `range` | boolean | – | Two picks make a range `start/end`. |
237
+ | `time` | boolean | – | A time field under the month – single days only. |
238
+ | `clearable` | boolean | – | A Clear button under the month. |
239
+ | `weeks` | boolean | – | The ISO calendar week in front of each row. |
240
+ | `week-start` | 0–6 | `Intl` | First day of the week: 0 Sunday, 1 Monday … |
241
+ | `weekend` | list | – | Weekdays not worked, greyed – `6,0`. |
242
+ | `days` | JSON | – | Per day `{ off, label, marks }`; also the property. |
243
+ | `today` | YYYY-MM-DD | – | Another today – for tests and docs. |
244
+ | `locale` | BCP 47 | `lang` | Language of the names and the week. |
245
+ | `floating` | boolean | – | A popover over the page: `show(anchor)`, `close()`. |
246
+ | `sheet` | boolean | – | Floating: a bottom sheet on a phone. |
247
+ | `open` | boolean | – | Floating: shown. |
248
+ | `align` | end | start | `end` | Floating: right or left edge on the anchor’s. |
249
+ | `label` | string | – | Names the month group for screen readers. |
250
+ | `today-label` | string | `Today` | Today button. |
251
+ | `prev-label` | string | `Previous month` | Names ‹. |
252
+ | `next-label` | string | `Next month` | Names ›. |
253
+ | `week-label` | string | `W · KW` | Head of the week column. |
254
+ | `time-label` | string | `Time` | Names the time field. |
255
+ | `clear-label` | string | `Clear` | Clear button. |
256
+ | `name` | string | – | Form field name (FormData key). |
257
+ | `disabled` | boolean | – | Disables the control. |
258
+ | `required` | boolean | – | A value is required; validity is set on the host. |
259
+
260
+ **Events:** `nk-change` `{ value, start, end, time }` – The value changed: a day picked – for a range the second one –, a new time, or Clear. The same day again is no change. · `nk-month` `{ month }` – Another month shown. · `nk-toggle` `{ open }` – Floating: opened or closed.
261
+
262
+ **Properties:** `value`, `start`, `end`, `month`, `days`, `open`, `form`, `validity` · **Methods:** `show(anchor)`, `close()`, `toggle(anchor)`, `focusDay()`, `checkValidity()`
263
+
264
+ **Replaces:** `.nk-calendar`, `.weeks`, `.cal-head`, `.cal-title`, `.cal-nav`, `.cal-grid`, `.cal-wd`, `.cal-week`, `.cal-day`, `.out`, `.off`, `.today`, `.selected`, `.start`, `.end`, `.in-range`, `.cal-marks`, `.cal-foot`, `.nk-pop`, `.floating`, `.sheet`, `.open`
265
+
266
+ ```html
267
+ <!-- equivalent class markup -->
268
+ <div style="display:flex;gap:16px;flex-wrap:wrap;align-items:flex-start">
269
+ <div class="nk-pop">
270
+ <div class="nk-calendar weeks">
271
+ <div class="cal-head"><div class="cal-title">June 2026</div><button class="cal-nav">Today</button><button class="cal-nav" aria-label="Previous month"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M15 6l-6 6 6 6"/></svg></button><button class="cal-nav" aria-label="Next month"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M9 6l6 6-6 6"/></svg></button></div>
272
+ <div class="cal-grid">
273
+ <span class="cal-wd">W</span><span class="cal-wd">Mo</span><span class="cal-wd">Tu</span><span class="cal-wd">We</span><span class="cal-wd">Th</span><span class="cal-wd">Fr</span><span class="cal-wd">Sa</span><span class="cal-wd">Su</span>
274
+ <span class="cal-week">23</span><button class="cal-day">1</button><button class="cal-day selected">2<span class="cal-marks"><i class="blue"></i></span></button><button class="cal-day">3</button><button class="cal-day off" title="Corpus Christi">4</button><button class="cal-day">5</button><button class="cal-day off">6</button><button class="cal-day off">7</button>
275
+ <span class="cal-week">24</span><button class="cal-day">8</button><button class="cal-day">9</button><button class="cal-day">10</button><button class="cal-day">11<span class="cal-marks"><i class="orange"></i><i class="red"></i></span></button><button class="cal-day">12</button><button class="cal-day off">13</button><button class="cal-day off">14</button>
276
+ <span class="cal-week">25</span><button class="cal-day">15</button><button class="cal-day">16</button><button class="cal-day today">17</button><button class="cal-day">18</button><button class="cal-day">19</button><button class="cal-day off">20</button><button class="cal-day off">21</button>
277
+ <span class="cal-week">26</span><button class="cal-day">22</button><button class="cal-day">23</button><button class="cal-day">24<span class="cal-marks"><i class="green"></i></span></button><button class="cal-day">25</button><button class="cal-day">26</button><button class="cal-day off">27</button><button class="cal-day off">28</button>
278
+ <span class="cal-week">27</span><button class="cal-day">29</button><button class="cal-day">30</button><button class="cal-day out">1</button><button class="cal-day out">2</button><button class="cal-day out">3</button><button class="cal-day out off">4</button><button class="cal-day out off">5</button>
279
+ <span class="cal-week">28</span><button class="cal-day out">6</button><button class="cal-day out">7</button><button class="cal-day out">8</button><button class="cal-day out">9</button><button class="cal-day out">10</button><button class="cal-day out off">11</button><button class="cal-day out off">12</button>
280
+ </div>
281
+ <div class="cal-foot"><input class="nk-input" type="time" value="09:30" aria-label="Time"><button class="cal-nav">Clear</button></div>
282
+ </div>
283
+ </div>
284
+ <div class="nk-pop">
285
+ <div class="nk-calendar weeks">
286
+ <div class="cal-head"><div class="cal-title">June 2026</div><button class="cal-nav">Today</button><button class="cal-nav" aria-label="Previous month"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M15 6l-6 6 6 6"/></svg></button><button class="cal-nav" aria-label="Next month"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M9 6l6 6-6 6"/></svg></button></div>
287
+ <div class="cal-grid">
288
+ <span class="cal-wd">W</span><span class="cal-wd">Mo</span><span class="cal-wd">Tu</span><span class="cal-wd">We</span><span class="cal-wd">Th</span><span class="cal-wd">Fr</span><span class="cal-wd">Sa</span><span class="cal-wd">Su</span>
289
+ <span class="cal-week">23</span><button class="cal-day">1</button><button class="cal-day">2</button><button class="cal-day">3</button><button class="cal-day off" title="Corpus Christi">4</button><button class="cal-day">5</button><button class="cal-day off">6</button><button class="cal-day off">7</button>
290
+ <span class="cal-week">24</span><button class="cal-day start">8</button><button class="cal-day in-range">9</button><button class="cal-day in-range">10</button><button class="cal-day in-range">11</button><button class="cal-day end">12</button><button class="cal-day off">13</button><button class="cal-day off">14</button>
291
+ <span class="cal-week">25</span><button class="cal-day">15</button><button class="cal-day">16</button><button class="cal-day today">17</button><button class="cal-day">18</button><button class="cal-day">19</button><button class="cal-day off">20</button><button class="cal-day off">21</button>
292
+ <span class="cal-week">26</span><button class="cal-day">22</button><button class="cal-day">23</button><button class="cal-day">24</button><button class="cal-day">25</button><button class="cal-day">26</button><button class="cal-day off">27</button><button class="cal-day off">28</button>
293
+ <span class="cal-week">27</span><button class="cal-day">29</button><button class="cal-day">30</button><button class="cal-day out">1</button><button class="cal-day out">2</button><button class="cal-day out">3</button><button class="cal-day out off">4</button><button class="cal-day out off">5</button>
294
+ <span class="cal-week">28</span><button class="cal-day out">6</button><button class="cal-day out">7</button><button class="cal-day out">8</button><button class="cal-day out">9</button><button class="cal-day out">10</button><button class="cal-day out off">11</button><button class="cal-day out off">12</button>
295
+ </div>
296
+ </div>
297
+ </div>
298
+ </div>
299
+ ```
300
+
301
+ **Small screens:** Floating with `sheet`: a bottom sheet with 44px cells, a thumb’s width; seven days and the week column still fit a 390px screen.
302
+
303
+ ### 3.6 `<nk-textarea>` – Textarea
304
+
305
+ Multi-line sibling of `nk-input`. The initial value is the `value` attribute or the element’s text content.
306
+
307
+ ```html
308
+ <nk-textarea name="bio" rows="3" placeholder="A sentence about you"></nk-textarea>
309
+ ```
310
+
311
+ | Attribute | Type | Default | Description |
312
+ |---|---|---|---|
313
+ | `value` | string | – | Current value. |
314
+ | `placeholder` | string | – | Placeholder text. |
315
+ | `rows` | number | – | Visible rows. |
316
+ | `name` | string | – | Form field name (FormData key). |
317
+ | `disabled` | boolean | – | Disables the control. |
318
+ | `required` | boolean | – | Required field. |
319
+ | `wide` | boolean | – | Full width. |
320
+
321
+ **Slots:** `(default)` – Initial text (used when `value` is absent).
322
+
323
+ **Events:** `nk-change` `{ value, name }` – On commit. · `nk-input` `{ value, name }` – On every keystroke.
324
+
325
+ **Replaces:** `.nk-textarea`, `.wide`
326
+
327
+ ```html
328
+ <!-- equivalent class markup -->
329
+ <textarea class="nk-textarea" name="bio" rows="3" placeholder="A sentence about you"></textarea>
330
+ ```
331
+
332
+ **Small screens:** Resizes vertically only; `wide` fills the row.
333
+
334
+ ### 3.7 `<nk-select>` – Select
335
+
336
+ Light-DOM `<option>` and `<optgroup>` children are copied into the shadow `<select>` and kept in step when a framework swaps them. The empty string is a valid value; a `value` naming no option leaves the selection alone. A `value` set before its option exists – a framework sets properties before children – waits and is applied once the option arrives.
337
+
338
+ ```html
339
+ <nk-select name="role" value="editor">
340
+ <option value="viewer">Viewer</option>
341
+ <option value="editor">Editor</option>
342
+ <option value="admin">Admin</option>
343
+ </nk-select>
344
+ ```
345
+
346
+ | Attribute | Type | Default | Description |
347
+ |---|---|---|---|
348
+ | `value` | string | – | Selected value. |
349
+ | `name` | string | – | Form field name (FormData key). |
350
+ | `disabled` | boolean | – | Disables the control. |
351
+ | `required` | boolean | – | Required field. |
352
+ | `compact` | boolean | – | 120px minimum width (`.compact`), e.g. inside a member row. |
353
+ | `wide` | boolean | – | Full width (`.nk-select.wide`). |
354
+
355
+ **Slots:** `(default)` – `<option>` / `<optgroup>` children – direct children only.
356
+
357
+ **Events:** `nk-change` `{ value, name }` – On selection.
358
+
359
+ **Properties:** `value`, `selectedIndex`, `options` · **Methods:** `refresh()`
360
+
361
+ **Replaces:** `.nk-select`, `.compact`
362
+
363
+ ```html
364
+ <!-- equivalent class markup -->
365
+ <select class="nk-select" name="role">
366
+ <option value="viewer">Viewer</option>
367
+ <option value="editor" selected>Editor</option>
368
+ <option value="admin">Admin</option>
369
+ </select>
370
+ ```
371
+
372
+ **Small screens:** Uses the native picker of the platform (`color-scheme` follows the theme).
373
+
374
+ ### 3.8 `<nk-switch>` – Switch
375
+
376
+ Renders `button.nk-switch[role=switch]`; the stylesheet keys the knob on `aria-checked`, the element does the toggling. Submits `value` (default `on`) when checked, nothing otherwise – like a checkbox.
377
+
378
+ ```html
379
+ <div style="display:flex;gap:20px;flex-wrap:wrap;align-items:center"><nk-switch name="notify" checked label="Email notifications"></nk-switch>
380
+ <nk-switch name="planned" checked>Planned</nk-switch><nk-switch name="progress">In progress</nk-switch><nk-switch name="done" text="Done" checked></nk-switch></div>
381
+ ```
382
+
383
+ | Attribute | Type | Default | Description |
384
+ |---|---|---|---|
385
+ | `checked` | boolean | – | On/off state. |
386
+ | `name` | string | – | Form field name (FormData key). |
387
+ | `disabled` | boolean | – | Disables the control. |
388
+ | `value` | string | `on` | Submitted value when checked. |
389
+ | `text` | string | – | Visible text beside the switch (alternative to the default slot). |
390
+ | `label` | string | – | Accessible name (`aria-label`) when there is no visible text. |
391
+
392
+ **Slots:** `(default)` – Visible text beside the switch; part of the hit area. Without it the element renders the bare button.
393
+
394
+ **Events:** `nk-change` `{ checked, value, name }` – On toggle.
395
+
396
+ **Methods:** `toggle()`
397
+
398
+ **Replaces:** `.nk-switch`, `.nk-switch-label`
399
+
400
+ ```html
401
+ <!-- equivalent class markup -->
402
+ <div style="display:flex;gap:20px;flex-wrap:wrap;align-items:center"><button class="nk-switch" role="switch" aria-checked="true" aria-label="Email notifications"></button>
403
+ <label class="nk-switch-label"><button class="nk-switch" role="switch" aria-checked="true"></button><span>Planned</span></label><label class="nk-switch-label"><button class="nk-switch" role="switch" aria-checked="false"></button><span>In progress</span></label><label class="nk-switch-label"><button class="nk-switch" role="switch" aria-checked="true"></button><span>Done</span></label></div>
404
+ ```
405
+
406
+ **Small screens:** 34×20px – below the 44px touch target. Give it a label row (`nk-field`) to enlarge the hit area.
407
+
408
+ ### 3.9 `<nk-check>` – Checkbox
409
+
410
+ A `label.nk-check` with a custom-drawn checkbox; the label text is slotted, so clicking it toggles the box.
411
+
412
+ ```html
413
+ <nk-check name="digest" value="weekly" checked>Weekly digest</nk-check>
414
+ <nk-check name="digest" value="mentions">Mentions only</nk-check>
415
+ ```
416
+
417
+ | Attribute | Type | Default | Description |
418
+ |---|---|---|---|
419
+ | `checked` | boolean | – | Checked state. |
420
+ | `indeterminate` | boolean | – | Mixed state (cleared on the next click). |
421
+ | `name` | string | – | Form field name (FormData key). |
422
+ | `disabled` | boolean | – | Disables the control. |
423
+ | `value` | string | `on` | Submitted value. |
424
+ | `required` | boolean | – | Must be checked to submit. |
425
+ | `text` | string | – | Label text (alternative to the default slot). |
426
+
427
+ **Slots:** `(default)` – Label text.
428
+
429
+ **Events:** `nk-change` `{ checked, value, name }` – On toggle.
430
+
431
+ **Replaces:** `.nk-check`
432
+
433
+ ```html
434
+ <!-- equivalent class markup -->
435
+ <label class="nk-check"><input type="checkbox" name="digest" value="weekly" checked>Weekly digest</label>
436
+ <label class="nk-check"><input type="checkbox" name="digest" value="mentions">Mentions only</label>
437
+ ```
438
+
439
+ **Small screens:** Row height ~24px; the whole label is the hit area.
440
+
441
+ ### 3.10 `<nk-radio>` – Radio
442
+
443
+ Same optics as `nk-check` with a round mark. Radios with the same `name` in the same tree and form form one group – across shadow roots, which native radios cannot do. One tab stop per group; arrow keys move, wrap and skip disabled entries. There is deliberately no `nk-radio-group`.
444
+
445
+ ```html
446
+ <nk-radio name="style" value="concise">Concise</nk-radio>
447
+ <nk-radio name="style" value="balanced" checked>Balanced</nk-radio>
448
+ <nk-radio name="style" value="detailed">Detailed</nk-radio>
449
+ ```
450
+
451
+ | Attribute | Type | Default | Description |
452
+ |---|---|---|---|
453
+ | `checked` | boolean | – | Selected; the last checked radio in markup wins. |
454
+ | `name` | string | – | Form field name (FormData key). |
455
+ | `disabled` | boolean | – | Disables the control. |
456
+ | `value` | string | – | Submitted value. |
457
+ | `required` | boolean | – | One of the group must be selected. |
458
+ | `text` | string | – | Label text (alternative to the default slot). |
459
+
460
+ **Slots:** `(default)` – Label text.
461
+
462
+ **Events:** `nk-change` `{ checked, value, name }` – On selection, also via arrow keys.
463
+
464
+ **Replaces:** `.nk-check`
465
+
466
+ ```html
467
+ <!-- equivalent class markup -->
468
+ <label class="nk-check"><input type="radio" name="style" value="concise">Concise</label>
469
+ <label class="nk-check"><input type="radio" name="style" value="balanced" checked>Balanced</label>
470
+ <label class="nk-check"><input type="radio" name="style" value="detailed">Detailed</label>
471
+ ```
472
+
473
+ **Small screens:** As `nk-check`.
474
+
475
+ ### 3.11 `<nk-slider>` – Slider
476
+
477
+ A range input with `accent-color` from the tokens, plus an optional value readout below.
478
+
479
+ ```html
480
+ <nk-slider name="size" min="12" max="18" value="14" unit="px" show-value></nk-slider>
481
+ ```
482
+
483
+ | Attribute | Type | Default | Description |
484
+ |---|---|---|---|
485
+ | `value` | number | – | Current value. |
486
+ | `min` | number | – | Minimum. |
487
+ | `max` | number | – | Maximum. |
488
+ | `step` | number | – | Step. |
489
+ | `name` | string | – | Form field name (FormData key). |
490
+ | `disabled` | boolean | – | Disables the control. |
491
+ | `show-value` | boolean | – | Shows the value below the slider. |
492
+ | `unit` | string | – | Suffix for the readout (e.g. `px`). |
493
+
494
+ **Events:** `nk-change` `{ value, name }` – On release. · `nk-input` `{ value, name }` – While dragging.
495
+
496
+ **Replaces:** `.nk-slider`, `.nk-slider-value`
497
+
498
+ ```html
499
+ <!-- equivalent class markup -->
500
+ <input type="range" class="nk-slider" name="size" min="12" max="18" value="14"><div class="nk-slider-value">14px</div>
501
+ ```
502
+
503
+ **Small screens:** 210px wide; the native thumb is touch-sized by the platform.
504
+
505
+ ### 3.12 `<nk-field>` – Field row
506
+
507
+ The settings row: label and description left, control right. Put any control – `nk-input`, `nk-switch`, `nk-select` – in the default slot. `stacked` puts the label above a full-width control (textareas, long descriptions) and sets `wide` on the control for you; `compact` shrinks the label to 12px tertiary text. Inside `nk-fields` both are on by default.
508
+
509
+ ```html
510
+ <nk-field label="Display name" desc="Shown next to your comments.">
511
+ <nk-input value="Ada Lovelace"></nk-input>
512
+ </nk-field>
513
+ <nk-field label="Email notifications">
514
+ <nk-switch checked></nk-switch>
515
+ </nk-field>
516
+ <nk-field label="Short bio" stacked>
517
+ <nk-textarea rows="2" placeholder="A sentence about you"></nk-textarea>
518
+ </nk-field>
519
+ ```
520
+
521
+ | Attribute | Type | Default | Description |
522
+ |---|---|---|---|
523
+ | `label` | string | – | Label text. |
524
+ | `desc` | string | – | Secondary description. |
525
+ | `stacked` | boolean | – | Label above a full-width control. |
526
+ | `compact` | boolean | – | Small tertiary label, no row padding. |
527
+
528
+ **Slots:** `(default)` – The control. · `label` – Rich label content (instead of the attribute). · `desc` – Rich description.
529
+
530
+ **Replaces:** `.nk-field`, `.f-label`, `.f-desc`, `.f-control`, `.stacked`, `.compact`
531
+
532
+ ```html
533
+ <!-- equivalent class markup -->
534
+ <div class="nk-field">
535
+ <div><div class="f-label">Display name</div><div class="f-desc">Shown next to your comments.</div></div>
536
+ <div class="f-control"><input class="nk-input" value="Ada Lovelace"></div>
537
+ </div>
538
+ <div class="nk-field">
539
+ <div><div class="f-label">Email notifications</div></div>
540
+ <div class="f-control"><button class="nk-switch" role="switch" aria-checked="true"></button></div>
541
+ </div>
542
+ <div class="nk-field stacked">
543
+ <div><div class="f-label">Short bio</div></div>
544
+ <div class="f-control"><textarea class="nk-textarea wide" rows="2" placeholder="A sentence about you"></textarea></div>
545
+ </div>
546
+ ```
547
+
548
+ **Small screens:** Stays a row; long descriptions wrap under the label. Use `stacked` where the control needs the whole width.
549
+
550
+ ### 3.13 `<nk-fields>` – Field grid
551
+
552
+ Several short fields in one row: a grid of `minmax(150px, 1fr)` columns that wraps as the width allows. Every `nk-field` inside renders itself stacked and compact – a 12px label above a full-width control – so nothing collides.
553
+
554
+ ```html
555
+ <nk-fields>
556
+ <nk-field label="Display name"><nk-input value="Ada Lovelace"></nk-input></nk-field>
557
+ <nk-field label="Email"><nk-input value="ada@acme.com"></nk-input></nk-field>
558
+ <nk-field label="Role"><nk-select value="editor"><option value="viewer">Viewer</option><option value="editor">Editor</option></nk-select></nk-field>
559
+ </nk-fields>
560
+ ```
561
+
562
+ | Attribute | Type | Default | Description |
563
+ |---|---|---|---|
564
+ | `fit` | boolean | – | The fields share the row instead of keeping 150px columns – two fields in a wide dialog are two halves. |
565
+
566
+ **Slots:** `(default)` – `nk-field` children.
567
+
568
+ **Replaces:** `.nk-fields`, `.fit`
569
+
570
+ ```html
571
+ <!-- equivalent class markup -->
572
+ <div class="nk-fields">
573
+ <div class="nk-field"><div><div class="f-label">Display name</div></div><div class="f-control"><input class="nk-input" value="Ada Lovelace"></div></div>
574
+ <div class="nk-field"><div><div class="f-label">Email</div></div><div class="f-control"><input class="nk-input" value="ada@acme.com"></div></div>
575
+ <div class="nk-field"><div><div class="f-label">Role</div></div><div class="f-control"><select class="nk-select"><option>Viewer</option><option selected>Editor</option></select></div></div>
576
+ </div>
577
+ ```
578
+
579
+ **Small screens:** Wraps to one or two columns on its own; no breakpoint needed.
580
+
581
+ ## Content elements (wave 1)
582
+
583
+ ### 3.14 `<nk-tag>` – Tag
584
+
585
+ The select option as Notion draws it, in its nine colours. The colour modifier class becomes the `color` attribute; without one it is the grey tag. Each pair is tuned per theme.
586
+
587
+ ```html
588
+ <nk-tag>Not started</nk-tag> <nk-tag color="brown">Archive</nk-tag> <nk-tag color="orange">Planned</nk-tag> <nk-tag color="yellow">Review</nk-tag> <nk-tag color="green">Done</nk-tag> <nk-tag color="blue">In progress</nk-tag> <nk-tag color="purple">Design</nk-tag> <nk-tag color="pink">Idea</nk-tag> <nk-tag color="red">Blocked</nk-tag>
589
+ ```
590
+
591
+ | Attribute | Type | Default | Description |
592
+ |---|---|---|---|
593
+ | `color` | gray | brown | orange | yellow | green | blue | purple | pink | red | – | Colour pair; grey without it. |
594
+
595
+ **Slots:** `(default)` – Tag text.
596
+
597
+ **Replaces:** `.nk-tag`, `.gray`, `.brown`, `.orange`, `.yellow`, `.green`, `.blue`, `.purple`, `.pink`, `.red`
598
+
599
+ ```html
600
+ <!-- equivalent class markup -->
601
+ <span class="nk-tag">Not started</span> <span class="nk-tag brown">Archive</span> <span class="nk-tag orange">Planned</span> <span class="nk-tag yellow">Review</span> <span class="nk-tag green">Done</span> <span class="nk-tag blue">In progress</span> <span class="nk-tag purple">Design</span> <span class="nk-tag pink">Idea</span> <span class="nk-tag red">Blocked</span>
602
+ ```
603
+
604
+ **Small screens:** Unchanged.
605
+
606
+ ### 3.15 `<nk-progress>` – Progress
607
+
608
+ A 60px bar with an optional label. `value`/`max` set the fill; the bar carries `role="progressbar"`.
609
+
610
+ ```html
611
+ <nk-progress value="72" label="72%"></nk-progress>
612
+ ```
613
+
614
+ | Attribute | Type | Default | Description |
615
+ |---|---|---|---|
616
+ | `value` | number | `0` | Current value. |
617
+ | `max` | number | `100` | Maximum. |
618
+ | `label` | string | – | Text after the bar. |
619
+ | `wide` | boolean | – | Fills its row – a property value, a panel; in a flex row the label stays beside it. |
620
+
621
+
622
+
623
+ **Replaces:** `.nk-progress`, `.nk-progress-label`, `.wide`
624
+
625
+ ```html
626
+ <!-- equivalent class markup -->
627
+ <span class="nk-progress"><i style="width:72%"></i></span><span class="nk-progress-label">72%</span>
628
+ ```
629
+
630
+ **Small screens:** Unchanged.
631
+
632
+ ### 3.16 `<nk-callout>` – Callout
633
+
634
+ One thought that must not be missed. The icon comes from the `icon` attribute or a `slot="icon"` node – the node itself, never wrapped.
635
+
636
+ ```html
637
+ <nk-callout icon="💡"><b>Core idea:</b> A callout carries one thought that must not be missed.</nk-callout>
638
+ ```
639
+
640
+ | Attribute | Type | Default | Description |
641
+ |---|---|---|---|
642
+ | `icon` | string | `💡` | Emoji or text icon. |
643
+
644
+ **Slots:** `(default)` – Body. · `icon` – Icon node (e.g. `<span slot="icon">📌</span>`).
645
+
646
+ **Replaces:** `.nk-callout`, `.c-icon`
647
+
648
+ ```html
649
+ <!-- equivalent class markup -->
650
+ <div class="nk-callout"><span class="c-icon">💡</span><div><b>Core idea:</b> A callout carries one thought that must not be missed.</div></div>
651
+ ```
652
+
653
+ **Small screens:** Unchanged; wraps with the text.
654
+
655
+ ### 3.17 `<nk-bookmark>` – Bookmark
656
+
657
+ Notion’s link block, filled from what you know of the page – `og:title`, `og:description`, `og:image`: title, two lines of description and the address with its icon on the left, the image on the right in a third, 240px at most. `title` is the heading, never a tooltip; without it the host name stands in. `url` shows an address other than `href`. Opens in a new tab unless `target` says otherwise; a part without a value is left out.
658
+
659
+ ```html
660
+ <div style="max-width:600px"><nk-bookmark href="https://notionkit.jungherz.com" title="NotionKit – the calm workspace look as CSS" desc="NotionKit is a pure CSS component library in the Notion idiom: sidebar, page tree, document shell, database views, settings and AI surfaces." favicon="/favicon.svg" cover="/covers/notionkit-og.jpg"></nk-bookmark></div>
661
+ ```
662
+
663
+ | Attribute | Type | Default | Description |
664
+ |---|---|---|---|
665
+ | `href` | URL | – | The link. |
666
+ | `title` | string | – | Title – og:title. |
667
+ | `desc` | string | – | Description, two lines – og:description. |
668
+ | `favicon` | URL | – | The site’s icon, 16px. |
669
+ | `cover` | URL | – | Preview image – og:image. |
670
+ | `url` | string | – | The address shown (default: href). |
671
+ | `target` | string | `_blank` | Link target. |
672
+
673
+ **Events:** `click` `(native)` – The link is a plain `<a>`.
674
+
675
+ **Properties:** `title`, `href`
676
+
677
+ **Replaces:** `.nk-bookmark`, `.bm-text`, `.bm-title`, `.bm-desc`, `.bm-url`, `.bm-favicon`, `.bm-cover`
678
+
679
+ ```html
680
+ <!-- equivalent class markup -->
681
+ <div style="max-width:600px"><a class="nk-bookmark" href="https://notionkit.jungherz.com" target="_blank" rel="noopener"><span class="bm-text"><span class="bm-title">NotionKit – the calm workspace look as CSS</span><span class="bm-desc">NotionKit is a pure CSS component library in the Notion idiom: sidebar, page tree, document shell, database views, settings and AI surfaces.</span><span class="bm-url"><img class="bm-favicon" src="/favicon.svg" alt=""><span>https://notionkit.jungherz.com</span></span></span><span class="bm-cover"><img src="/covers/notionkit-og.jpg" alt=""></span></a></div>
682
+ ```
683
+
684
+ **Small screens:** The image keeps its third and is cropped at the centre; title and address end in an ellipsis.
685
+
686
+ ### 3.18 `<nk-divider>` – Divider
687
+
688
+ A hairline `<hr>` with block spacing.
689
+
690
+ ```html
691
+ <nk-divider></nk-divider>
692
+ ```
693
+
694
+ _No attributes._
695
+
696
+
697
+
698
+ **Replaces:** `.nk-divider`
699
+
700
+ ```html
701
+ <!-- equivalent class markup -->
702
+ <hr class="nk-divider">
703
+ ```
704
+
705
+ **Small screens:** Unchanged.
706
+
707
+ ### 3.19 `<nk-heading>` – Heading
708
+
709
+ A section heading. `level` chooses the real heading element (h1–h4), so the document outline stays honest.
710
+
711
+ ```html
712
+ <nk-heading>Section heading</nk-heading>
713
+ ```
714
+
715
+ | Attribute | Type | Default | Description |
716
+ |---|---|---|---|
717
+ | `level` | 1 | 2 | 3 | 4 | `2` | Heading level. |
718
+
719
+ **Slots:** `(default)` – Heading text.
720
+
721
+ **Replaces:** `.nk-heading`
722
+
723
+ ```html
724
+ <!-- equivalent class markup -->
725
+ <h2 class="nk-heading">Section heading</h2>
726
+ ```
727
+
728
+ **Small screens:** Unchanged.
729
+
730
+ ### 3.20 `<nk-toggle>` – Toggle block
731
+
732
+ A `<details>` block. The summary is rendered inside the element (its marker is a pseudo-element and cannot be styled on slotted content); the body is slotted.
733
+
734
+ ```html
735
+ <nk-toggle label="Details" open>Folded content lives here.</nk-toggle>
736
+ ```
737
+
738
+ | Attribute | Type | Default | Description |
739
+ |---|---|---|---|
740
+ | `label` | string | – | Summary text. |
741
+ | `open` | boolean | – | Expanded state, reflected both ways. |
742
+
743
+ **Slots:** `(default)` – Folded content. · `label` – Rich summary content.
744
+
745
+ **Events:** `nk-toggle` `{ open }` – On open/close.
746
+
747
+ **Replaces:** `.nk-toggle`, `.toggle-body`
748
+
749
+ ```html
750
+ <!-- equivalent class markup -->
751
+ <details class="nk-toggle" open><summary>Details</summary><div class="toggle-body">Folded content lives here.</div></details>
752
+ ```
753
+
754
+ **Small screens:** Unchanged.
755
+
756
+ ### 3.21 `<nk-todo>` – To-do
757
+
758
+ Checkbox line with strike-through when done. Form-associated like `nk-check`.
759
+
760
+ ```html
761
+ <nk-todo checked>Write the docs</nk-todo>
762
+ <nk-todo>Ship it</nk-todo>
763
+ ```
764
+
765
+ | Attribute | Type | Default | Description |
766
+ |---|---|---|---|
767
+ | `checked` | boolean | – | Done. |
768
+ | `name` | string | – | Form field name (FormData key). |
769
+ | `disabled` | boolean | – | Disables the control. |
770
+ | `value` | string | `on` | Submitted value. |
771
+
772
+ **Slots:** `(default)` – Task text.
773
+
774
+ **Events:** `nk-change` `{ checked, value, name }` – On toggle.
775
+
776
+ **Replaces:** `.nk-todo`
777
+
778
+ ```html
779
+ <!-- equivalent class markup -->
780
+ <label class="nk-todo"><input type="checkbox" checked><span>Write the docs</span></label>
781
+ <label class="nk-todo"><input type="checkbox"><span>Ship it</span></label>
782
+ ```
783
+
784
+ **Small screens:** Unchanged.
785
+
786
+ ### 3.22 `<nk-kbd>` – Key cap
787
+
788
+ A keyboard key, e.g. in shortcut hints.
789
+
790
+ ```html
791
+ <nk-kbd>⌘</nk-kbd> <nk-kbd>K</nk-kbd>
792
+ ```
793
+
794
+ _No attributes._
795
+
796
+ **Slots:** `(default)` – Key label.
797
+
798
+ **Replaces:** `.nk-kbd`
799
+
800
+ ```html
801
+ <!-- equivalent class markup -->
802
+ <kbd class="nk-kbd">⌘</kbd> <kbd class="nk-kbd">K</kbd>
803
+ ```
804
+
805
+ **Small screens:** Unchanged.
806
+
807
+ ### 3.23 `<nk-code>` – Code block
808
+
809
+ Pre-formatted block with a language badge. Whitespace is kept as written; escape `<` as `&lt;`. With `highlight`, HTML tags and attributes are coloured.
810
+
811
+ ```html
812
+ <nk-code lang="html" highlight>&lt;nk-btn variant="primary"&gt;Save&lt;/nk-btn&gt;</nk-code>
813
+ ```
814
+
815
+ | Attribute | Type | Default | Description |
816
+ |---|---|---|---|
817
+ | `lang` | string | – | Language badge, top right. |
818
+ | `highlight` | boolean | – | Colour HTML tags/attributes. |
819
+
820
+ **Slots:** `(default)` – The code, as text.
821
+
822
+ **Replaces:** `.nk-code`, `.lang`, `.tag`, `.attr`
823
+
824
+ ```html
825
+ <!-- equivalent class markup -->
826
+ <div class="nk-code"><span class="lang">html</span>&lt;<span class="tag">nk-btn</span> <span class="attr">variant</span>="primary"&gt;Save&lt;/<span class="tag">nk-btn</span>&gt;</div>
827
+ ```
828
+
829
+ **Small screens:** Scrolls horizontally instead of wrapping.
830
+
831
+ ### 3.24 `<nk-quote>` – Quote
832
+
833
+ A block quote with an optional citation line.
834
+
835
+ ```html
836
+ <nk-quote cite="Unknown">The best interface is the one that gets out of the way.</nk-quote>
837
+ ```
838
+
839
+ | Attribute | Type | Default | Description |
840
+ |---|---|---|---|
841
+ | `cite` | string | – | Citation text. |
842
+
843
+ **Slots:** `(default)` – Quote text.
844
+
845
+ **Replaces:** `.nk-quote`, `.q-cite`
846
+
847
+ ```html
848
+ <!-- equivalent class markup -->
849
+ <blockquote class="nk-quote">The best interface is the one that gets out of the way.<cite class="q-cite">Unknown</cite></blockquote>
850
+ ```
851
+
852
+ **Small screens:** Unchanged.
853
+
854
+ ## App shell & navigation (wave 2)
855
+
856
+ ### 3.25 `<nk-app>` – App shell
857
+
858
+ The outermost element of a workspace app: a full-height flex row with the sidebar slot left and `main.nk-main` right. Everything in the default slot – `nk-topbar`, `nk-page` – becomes a flex child of the main column.
859
+
860
+ ```html
861
+ <nk-app>
862
+ <nk-sidebar slot="sidebar">
863
+ <nk-workspace-switcher slot="workspace" name="MonaHilft"></nk-workspace-switcher>
864
+ <nk-tree>
865
+ <nk-tree-item icon="🔍">Search<span slot="end" class="nk-kbd-hint"><nk-kbd>⌘</nk-kbd><nk-kbd>K</nk-kbd></span></nk-tree-item>
866
+ <nk-tree-item icon="🏠" active>Home</nk-tree-item>
867
+ <nk-tree-item icon="📥">Inbox</nk-tree-item>
868
+ </nk-tree>
869
+ <nk-tree-item slot="footer" icon="⚙️">Settings</nk-tree-item>
870
+ </nk-sidebar>
871
+ <nk-topbar>
872
+ <nk-breadcrumb><span>📊 Project overview</span></nk-breadcrumb>
873
+ <nk-btn slot="actions" variant="share">Share</nk-btn>
874
+ <nk-theme-toggle slot="actions"></nk-theme-toggle>
875
+ </nk-topbar>
876
+ <div class="nk-page-scroll"><div class="nk-page" style="padding-top:16px">
877
+ <h1 class="nk-page-title" style="font-size:28px">NotionKit MVP</h1>
878
+ <p class="lead">A calm, document-centric workspace app – built from elements only.</p>
879
+ </div></div>
880
+ </nk-app>
881
+ ```
882
+
883
+ | Attribute | Type | Default | Description |
884
+ |---|---|---|---|
885
+ | `peek-inset` | boolean | – | The main column makes room for the side peek – `<nk-peek inset>` sets it while open. |
886
+
887
+ **Slots:** `sidebar` – An `nk-sidebar`. · `(default)` – Topbar, page – the main column.
888
+
889
+ **Replaces:** `.nk-app`, `.nk-main`, `.peek-inset`
890
+
891
+ ```html
892
+ <!-- equivalent class markup -->
893
+ <div class="nk-app">
894
+ <aside class="nk-sidebar">
895
+ <div class="nk-workspace"><div class="avatar">M</div><span>MonaHilft</span><span class="chev">⌄</span></div>
896
+ <div class="nk-sidebar-scroll">
897
+ <div class="nk-tree-item"><span class="icon">🔍</span><span class="label">Search</span><span class="nk-kbd-hint"><kbd class="nk-kbd">⌘</kbd><kbd class="nk-kbd">K</kbd></span></div>
898
+ <div class="nk-tree-item active"><span class="icon">🏠</span><span class="label">Home</span></div>
899
+ <div class="nk-tree-item"><span class="icon">📥</span><span class="label">Inbox</span></div>
900
+ </div>
901
+ <div class="nk-sidebar-footer">
902
+ <div class="nk-tree-item"><span class="icon">⚙️</span><span class="label">Settings</span></div>
903
+ </div>
904
+ </aside>
905
+ <main class="nk-main">
906
+ <div class="nk-topbar">
907
+ <nav class="nk-breadcrumb"><span class="crumb current">📊 Project overview</span></nav>
908
+ <div class="nk-topbar-actions"><button class="nk-topbar-btn nk-share-btn">Share</button><button class="nk-topbar-btn nk-theme-toggle">🌙</button></div>
909
+ </div>
910
+ <div class="nk-page-scroll"><div class="nk-page" style="padding-top:16px">
911
+ <h1 class="nk-page-title" style="font-size:28px">NotionKit MVP</h1>
912
+ <p class="lead">A calm, document-centric workspace app – built from elements only.</p>
913
+ </div></div>
914
+ </main>
915
+ </div>
916
+ ```
917
+
918
+ **Small screens:** Below 860px the sidebar is hidden; open it as a drawer with `sidebar.open = true`.
919
+
920
+ ### 3.26 `<nk-sidebar>` – Sidebar
921
+
922
+ The left rail: workspace slot on top, a scrolling default slot for the tree, a pinned footer slot. Footer tree items automatically get `compact` (26px rows). The host is `display: contents`, so the `aside` is a direct flex child of the app – exactly like the class markup. `collapsible` adds Notion’s « beside the workspace row, shown while the pointer is over the sidebar: on the desktop it collapses the sidebar – it slides out, the main column takes the width, nothing in it takes focus – and `<nk-btn variant="sidebar">` in the topbar shows and brings it back; ⌘\ or Ctrl+\ toggles it. `collapsed` is the state, `nk-collapse` the moment to keep it.
923
+
924
+ ```html
925
+ <div style="display:flex;height:100%"><nk-sidebar collapsible collapse-label="Close sidebar">
926
+ <nk-workspace-switcher slot="workspace" name="MonaHilft"></nk-workspace-switcher>
927
+ <nk-tree>
928
+ <nk-tree-item icon="🏠" active>Home</nk-tree-item>
929
+ <nk-tree-item icon="📥">Inbox</nk-tree-item>
930
+ </nk-tree>
931
+ <nk-tree-item slot="footer" icon="⚙️">Settings</nk-tree-item>
932
+ <nk-tree-item slot="footer" icon="🗑️">Trash</nk-tree-item>
933
+ </nk-sidebar></div>
934
+ ```
935
+
936
+ | Attribute | Type | Default | Description |
937
+ |---|---|---|---|
938
+ | `open` | boolean | – | Drawer state on small screens (no effect on desktop). |
939
+ | `collapsible` | boolean | – | Shows the « that collapses it on the desktop, and ⌘\. |
940
+ | `collapsed` | boolean | – | Collapsed on the desktop (no effect on a phone). |
941
+ | `collapse-label` | string | `Close sidebar` | Name and tooltip of the «. |
942
+
943
+ **Slots:** `workspace` – `nk-workspace-switcher`. · `(default)` – The tree (scrolls). · `footer` – Pinned bottom rows (Settings, Trash).
944
+
945
+ **Events:** `nk-toggle` `{ open }` – Drawer opened/closed. · `nk-collapse` `{ collapsed }` – Collapsed / expanded on the desktop.
946
+
947
+ **Properties:** `open`, `collapsed` · **Methods:** `show()`, `close()`, `toggle()`, `collapse()`, `expand()`, `toggleCollapsed()`
948
+
949
+ **Replaces:** `.nk-sidebar`, `.nk-sidebar-head`, `.nk-sidebar-collapse`, `.nk-sidebar-scroll`, `.nk-sidebar-footer`, `.nk-sidebar-backdrop`, `.open`, `.collapsed`
950
+
951
+ ```html
952
+ <!-- equivalent class markup -->
953
+ <div style="display:flex;height:100%"><aside class="nk-sidebar">
954
+ <div class="nk-sidebar-head"><div class="nk-workspace"><div class="avatar">M</div><span>MonaHilft</span><span class="chev">⌄</span></div><button class="nk-sidebar-collapse" aria-label="Close sidebar"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="m18 17-5-5 5-5M11 17l-5-5 5-5"/></svg></button></div>
955
+ <div class="nk-sidebar-scroll">
956
+ <div class="nk-tree-item active"><span class="icon">🏠</span><span class="label">Home</span></div>
957
+ <div class="nk-tree-item"><span class="icon">📥</span><span class="label">Inbox</span></div>
958
+ </div>
959
+ <div class="nk-sidebar-footer">
960
+ <div class="nk-tree-item"><span class="icon">⚙️</span><span class="label">Settings</span></div>
961
+ <div class="nk-tree-item"><span class="icon">🗑️</span><span class="label">Trash</span></div>
962
+ </div>
963
+ </aside></div>
964
+ ```
965
+
966
+ **Small screens:** Hidden below 860px. `open` shows it as a drawer over the page with a scrim – NotionKit’s own rules since 1.7.0 (`.nk-sidebar.open`, `.nk-sidebar-backdrop`), the same as the class markup’s; `<nk-btn variant="sidebar">` in the topbar is the ☰ that opens it. Escape and the scrim close it. The drawer slides in over 240ms and the scrim fades, closing runs backwards – CSS only (`transition-behavior: allow-discrete` + `@starting-style`; older browsers switch hard, reduced motion snaps). In landscape the drawer grows by the left safe-area inset, so its rows clear the Dynamic Island.
967
+
968
+ ### 3.27 `<nk-workspace-switcher>` – Workspace switcher
969
+
970
+ The row at the very top of the sidebar. A click toggles `open` and shows whatever sits in the `menu` slot below it (an `nk-menu`, from wave 4); outside clicks and Escape close it.
971
+
972
+ ```html
973
+ <div style="background:var(--nk-bg-sidebar);border-radius:8px;max-width:260px"><nk-workspace-switcher name="MonaHilft"></nk-workspace-switcher></div>
974
+ ```
975
+
976
+ | Attribute | Type | Default | Description |
977
+ |---|---|---|---|
978
+ | `name` | string | – | Workspace name. |
979
+ | `avatar` | string | – | Avatar text (default: first letter of the name). |
980
+ | `open` | boolean | – | Menu shown. |
981
+
982
+ **Slots:** `avatar` – Custom avatar node. · `menu` – The popover content.
983
+
984
+ **Events:** `nk-toggle` `{ open }` – Menu opened/closed. · `nk-select` `(from the menu)` – Bubbles up from a menu item; the menu closes.
985
+
986
+ **Methods:** `show()`, `close()`, `toggle()`
987
+
988
+ **Replaces:** `.nk-workspace`, `.avatar`, `.chev`
989
+
990
+ ```html
991
+ <!-- equivalent class markup -->
992
+ <div style="background:var(--nk-bg-sidebar);border-radius:8px;max-width:260px"><div class="nk-workspace"><div class="avatar">M</div><span>MonaHilft</span><span class="chev">⌄</span></div></div>
993
+ ```
994
+
995
+ **Small screens:** Unchanged.
996
+
997
+ ### 3.28 `<nk-section-label>` – Section label
998
+
999
+ Small uppercase-ish heading between tree sections. With `addable` a + appears on hover and fires `nk-action`.
1000
+
1001
+ ```html
1002
+ <div style="background:var(--nk-bg-sidebar);border-radius:8px;max-width:244px;padding:0 8px 6px"><nk-section-label addable>Favourites</nk-section-label><nk-tree-item icon="📊">Project overview</nk-tree-item></div>
1003
+ ```
1004
+
1005
+ | Attribute | Type | Default | Description |
1006
+ |---|---|---|---|
1007
+ | `addable` | boolean | – | Shows the + on hover. |
1008
+ | `label` | string | – | Text (alternative to the slot). |
1009
+
1010
+ **Slots:** `(default)` – Label text.
1011
+
1012
+ **Events:** `nk-action` `{ action: 'add' }` – + clicked.
1013
+
1014
+ **Replaces:** `.nk-section-label`, `.plus`
1015
+
1016
+ ```html
1017
+ <!-- equivalent class markup -->
1018
+ <div style="background:var(--nk-bg-sidebar);border-radius:8px;max-width:244px;padding:0 8px 6px"><div class="nk-section-label">Favourites <span class="plus">+</span></div><div class="nk-tree-item"><span class="icon">📊</span><span class="label">Project overview</span><span class="actions"><span>+</span><span>⋯</span></span></div></div>
1019
+ ```
1020
+
1021
+ **Small screens:** Unchanged.
1022
+
1023
+ ### 3.29 `<nk-tree>` – Tree
1024
+
1025
+ Container for `nk-tree-item`s: keeps exactly one item `active` (listening to `nk-select` at any depth), gives the whole tree a single tab stop with arrow-key navigation (↑↓ move, → expands or enters, ← collapses or leaves, Home/End), and renders items from `tree.data`. `tree.value` is read-only – select programmatically with `item.select()` or the `active` attribute. Section labels may sit between items; their + fires `nk-action { action: 'add' }` without a value.
1026
+
1027
+ ```html
1028
+ <div style="background:var(--nk-bg-sidebar);border-radius:8px;max-width:244px;padding:6px 8px"><nk-tree>
1029
+ <nk-section-label addable>Favourites</nk-section-label>
1030
+ <nk-tree-item icon="📊" open>Project overview
1031
+ <nk-tree-item icon="🚀" active>NotionKit MVP</nk-tree-item>
1032
+ <nk-tree-item icon="🎙️">Voice-Office-Hub</nk-tree-item>
1033
+ </nk-tree-item>
1034
+ <nk-tree-item icon="🧠">Knowledge base
1035
+ <nk-tree-item icon="📄">Onboarding</nk-tree-item>
1036
+ </nk-tree-item>
1037
+ <nk-tree-item icon="🎨">Design system</nk-tree-item>
1038
+ </nk-tree></div>
1039
+ ```
1040
+
1041
+ | Attribute | Type | Default | Description |
1042
+ |---|---|---|---|
1043
+ | `manual` | boolean | – | Do not move `active` automatically. |
1044
+
1045
+ **Slots:** `(default)` – `nk-tree-item` and `nk-section-label` children.
1046
+
1047
+ **Events:** `nk-select` `{ value, label, href, item }` – Bubbles from the selected item. · `nk-toggle` `{ open, value }` – A branch opened/closed. · `nk-action` `{ action, value }` – Hover action of an item.
1048
+
1049
+ **Properties:** `data`, `activeItem`, `value`
1050
+
1051
+ **Replaces:**
1052
+
1053
+ ```html
1054
+ <!-- equivalent class markup -->
1055
+ <div style="background:var(--nk-bg-sidebar);border-radius:8px;max-width:244px;padding:6px 8px"><div>
1056
+ <div class="nk-section-label">Favourites <span class="plus">+</span></div>
1057
+ <div class="nk-tree-item"><span class="nk-toggle-arrow open">▸</span><span class="icon">📊</span><span class="label">Project overview</span><span class="actions"><span>+</span><span>⋯</span></span></div>
1058
+ <div class="nk-tree-children">
1059
+ <div class="nk-tree-item active"><span class="icon">🚀</span><span class="label">NotionKit MVP</span><span class="actions"><span>+</span><span>⋯</span></span></div>
1060
+ <div class="nk-tree-item"><span class="icon">🎙️</span><span class="label">Voice-Office-Hub</span><span class="actions"><span>+</span><span>⋯</span></span></div>
1061
+ </div>
1062
+ <div class="nk-tree-item"><span class="nk-toggle-arrow">▸</span><span class="icon">🧠</span><span class="label">Knowledge base</span><span class="actions"><span>+</span><span>⋯</span></span></div>
1063
+ <div class="nk-tree-children collapsed">
1064
+ <div class="nk-tree-item"><span class="icon">📄</span><span class="label">Onboarding</span></div>
1065
+ </div>
1066
+ <div class="nk-tree-item"><span class="icon">🎨</span><span class="label">Design system</span><span class="actions"><span>+</span><span>⋯</span></span></div>
1067
+ </div></div>
1068
+ ```
1069
+
1070
+ **Small screens:** Rows are 28px; raise the hit area in a touch drawer via the sidebar’s `open` state styling of your own.
1071
+
1072
+ ### 3.30 `<nk-tree-item>` – Tree item
1073
+
1074
+ One row of the page tree – and its children box. Text content is the label, nested `nk-tree-item`s are the children (the arrow appears only then), `slot="icon"` and `slot="end"` go where they say. Hover actions +/⋯ report through `nk-action`; a click fires `nk-select` (cancelable). Outside an `nk-tree` (sidebar footer) an item marks itself `active` on click unless the event is cancelled.
1075
+
1076
+ ```html
1077
+ <div style="background:var(--nk-bg-sidebar);border-radius:8px;max-width:244px;padding:6px 8px">
1078
+ <nk-tree-item icon="🔍" value="search">Search<span slot="end" class="nk-kbd-hint"><nk-kbd>⌘</nk-kbd><nk-kbd>K</nk-kbd></span></nk-tree-item>
1079
+ <nk-tree-item icon="📊" active>Project overview</nk-tree-item>
1080
+ <nk-tree-item icon="🎨">Design system</nk-tree-item>
1081
+ </div>
1082
+ ```
1083
+
1084
+ | Attribute | Type | Default | Description |
1085
+ |---|---|---|---|
1086
+ | `icon` | string | – | Emoji/text icon (or `slot="icon"`). |
1087
+ | `label` | string | – | Label (alternative to text content). |
1088
+ | `value` | string | – | Value reported in events (default: label). |
1089
+ | `href` | URL | – | Navigate on select. |
1090
+ | `active` | boolean | – | Current item. |
1091
+ | `open` | boolean | – | Children expanded. |
1092
+ | `compact` | boolean | – | 26px row (footer, settings nav). |
1093
+ | `no-actions` | boolean | – | Hide the +/⋯ hover actions. |
1094
+
1095
+ **Slots:** `(default)` – Label text and nested `nk-tree-item`s. · `icon` – Icon node. · `end` – Trailing content, e.g. `<span slot="end" class="nk-kbd-hint">` with `nk-kbd`s (hides the actions).
1096
+
1097
+ **Events:** `nk-select` `{ value, label, href, item }` – Row clicked / Enter. `preventDefault()` keeps it from becoming active. · `nk-toggle` `{ open, value }` – Arrow clicked. · `nk-action` `{ action: 'add' | 'more', value }` – Hover action clicked.
1098
+
1099
+ **Properties:** `label`, `value`, `active`, `open`, `hasChildren` · **Methods:** `select()`, `toggle()`, `focus()`
1100
+
1101
+ **Replaces:** `.nk-tree-item`, `.icon`, `.label`, `.actions`, `.active`, `.compact`, `.nk-tree-children`, `.collapsed`, `.nk-toggle-arrow`, `.open`, `.nk-kbd-hint`
1102
+
1103
+ ```html
1104
+ <!-- equivalent class markup -->
1105
+ <div style="background:var(--nk-bg-sidebar);border-radius:8px;max-width:244px;padding:6px 8px">
1106
+ <div class="nk-tree-item"><span class="icon">🔍</span><span class="label">Search</span><span class="nk-kbd-hint"><kbd class="nk-kbd">⌘</kbd><kbd class="nk-kbd">K</kbd></span></div>
1107
+ <div class="nk-tree-item active"><span class="icon">📊</span><span class="label">Project overview</span><span class="actions"><span>+</span><span>⋯</span></span></div>
1108
+ <div class="nk-tree-item"><span class="icon">🎨</span><span class="label">Design system</span><span class="actions"><span>+</span><span>⋯</span></span></div>
1109
+ </div>
1110
+ ```
1111
+
1112
+ **Small screens:** 28px rows (26px with `compact`) – below the 44px touch target; the tree does not force a height.
1113
+
1114
+ ### 3.31 `<nk-topbar>` – Top bar
1115
+
1116
+ The 44px bar above the page: breadcrumb in the default slot, buttons in the `actions` slot (right-aligned). Use `nk-btn variant="topbar"` / `"share"` and `nk-theme-toggle` there, and `<span class="nk-topbar-meta">` for passive text such as “Edited 2 min ago”.
1117
+
1118
+ ```html
1119
+ <div style="border:1px solid var(--nk-border);border-radius:8px;display:flex;flex-direction:column"><nk-topbar>
1120
+ <nk-breadcrumb><span>📊 Project overview</span><span>🚀 NotionKit MVP</span></nk-breadcrumb>
1121
+ <span slot="actions" class="nk-topbar-meta">Last edited 2 min ago</span>
1122
+ <nk-btn slot="actions" variant="share">Share</nk-btn>
1123
+ <nk-btn slot="actions" variant="topbar">⭐</nk-btn>
1124
+ <nk-theme-toggle slot="actions"></nk-theme-toggle>
1125
+ </nk-topbar></div>
1126
+ ```
1127
+
1128
+ _No attributes._
1129
+
1130
+ **Slots:** `(default)` – Breadcrumb / title. · `actions` – Buttons on the right.
1131
+
1132
+ **Replaces:** `.nk-topbar`, `.nk-topbar-actions`, `.nk-topbar-btn`, `.nk-topbar-meta`, `.nk-share-btn`
1133
+
1134
+ ```html
1135
+ <!-- equivalent class markup -->
1136
+ <div style="border:1px solid var(--nk-border);border-radius:8px;display:flex;flex-direction:column"><div class="nk-topbar">
1137
+ <nav class="nk-breadcrumb"><span class="crumb">📊 Project overview</span><span class="sep">/</span><span class="crumb current">🚀 NotionKit MVP</span></nav>
1138
+ <div class="nk-topbar-actions"><span class="nk-topbar-meta">Last edited 2 min ago</span><button class="nk-topbar-btn nk-share-btn">Share</button><button class="nk-topbar-btn">⭐</button><button class="nk-topbar-btn nk-theme-toggle">🌙</button></div>
1139
+ </div></div>
1140
+ ```
1141
+
1142
+ **Small screens:** Below 860px only the last crumb stays and ends in an ellipsis, `nk-topbar-meta` hides, and the actions keep to one line – as in Notion’s mobile app.
1143
+
1144
+ ### 3.32 `<nk-breadcrumb>` – Breadcrumb
1145
+
1146
+ Give it plain `<span>` or `<a>` children; they are cloned into the bar with separators between them and the last one marked current (or the child with a `current` attribute). Text changes, added or removed children are picked up automatically (`refresh()` only for what the observer cannot see). Clicking a crumb fires `nk-select` and forwards the click to the original child, so links navigate exactly once.
1147
+
1148
+ ```html
1149
+ <nk-breadcrumb><a href="#">📊 Project overview</a><span>🚀 NotionKit MVP</span></nk-breadcrumb>
1150
+ ```
1151
+
1152
+ | Attribute | Type | Default | Description |
1153
+ |---|---|---|---|
1154
+ | `separator` | string | `/` | Separator glyph. |
1155
+
1156
+ **Slots:** `(default)` – Crumb children (direct children only, no `slot` attribute).
1157
+
1158
+ **Events:** `nk-select` `{ index, value, label, href, current }` – Crumb clicked; `preventDefault()` stops the forwarded click.
1159
+
1160
+ **Methods:** `refresh()`
1161
+
1162
+ **Replaces:** `.nk-breadcrumb`, `.crumb`, `.sep`, `.current`
1163
+
1164
+ ```html
1165
+ <!-- equivalent class markup -->
1166
+ <nav class="nk-breadcrumb"><a class="crumb" href="#">📊 Project overview</a><span class="sep">/</span><span class="crumb current">🚀 NotionKit MVP</span></nav>
1167
+ ```
1168
+
1169
+ **Small screens:** Stays on one line; keep crumbs short.
1170
+
1171
+ ### 3.33 `<nk-theme-toggle>` – Theme toggle
1172
+
1173
+ The ☀️/🌙 button. Flips `data-theme` on `<html>`, remembers the choice in `localStorage`, applies a stored or system preference on first connect when `<html>` has no theme yet, and accepts `postMessage({ nkTheme })` from a parent page. `apply(theme)` does everything a click does: sets, persists and fires `nk-change`.
1174
+
1175
+ ```html
1176
+ <nk-theme-toggle></nk-theme-toggle>
1177
+ ```
1178
+
1179
+ | Attribute | Type | Default | Description |
1180
+ |---|---|---|---|
1181
+ | `storage-key` | string | `nk-theme` | localStorage key. |
1182
+ | `title` | string | – | Tooltip. |
1183
+
1184
+ **Events:** `nk-change` `{ value: 'light' | 'dark' }` – Theme applied.
1185
+
1186
+ **Properties:** `value` · **Methods:** `apply(theme)`
1187
+
1188
+ **Replaces:** `.nk-theme-toggle`
1189
+
1190
+ ```html
1191
+ <!-- equivalent class markup -->
1192
+ <button class="nk-topbar-btn nk-theme-toggle">🌙</button>
1193
+ ```
1194
+
1195
+ **Small screens:** Unchanged.
1196
+
1197
+ ### 3.34 `<nk-tab-bar>` – Tab bar (mobile)
1198
+
1199
+ The thumb-reachable twin of the sidebar for phones and installed PWAs. Put it last inside `nk-app`: it is slotted into the main column below the scrolling page, so it never moves and no bottom padding is needed. Keeps exactly one `nk-tab-bar-item` active (listening to `nk-select`); a `drawer` item opens the sidebar instead. Needs NotionKit CSS 1.2.0.
1200
+
1201
+ ```html
1202
+ <div style="max-width:390px;border:1px solid var(--nk-border);border-radius:12px;overflow:hidden"><nk-tab-bar always value="inbox">
1203
+ <nk-tab-bar-item icon="🏠" value="home">Home</nk-tab-bar-item>
1204
+ <nk-tab-bar-item icon="📥" value="inbox">Inbox</nk-tab-bar-item>
1205
+ <nk-tab-bar-item icon="🔍" value="search">Search</nk-tab-bar-item>
1206
+ <nk-tab-bar-item icon="⚙️" value="settings">Settings</nk-tab-bar-item>
1207
+ <nk-tab-bar-item icon="☰" drawer>More</nk-tab-bar-item>
1208
+ </nk-tab-bar></div>
1209
+ ```
1210
+
1211
+ | Attribute | Type | Default | Description |
1212
+ |---|---|---|---|
1213
+ | `value` | string | – | Active item value (default: the item with `active`, else the first). |
1214
+ | `always` | boolean | – | Visible at every width, not only below 860px. |
1215
+ | `fixed` | boolean | – | Pinned to the viewport bottom instead of sitting in the column – for standalone PWAs; a spacer keeps its height (`--nk-tab-bar-height` + safe area) in the flow. |
1216
+ | `floating` | boolean | – | A fixed capsule with rounded corners instead of the full-width bar. |
1217
+ | `label` | string | – | `aria-label` of the `nav`. |
1218
+
1219
+ **Slots:** `(default)` – `nk-tab-bar-item` children, up to five.
1220
+
1221
+ **Events:** `nk-change` `{ value }` – Active item changed. · `nk-select` `{ value, label, href, item, drawer }` – Bubbles from the tapped item; cancelable.
1222
+
1223
+ **Properties:** `value`, `items`
1224
+
1225
+ **Replaces:** `.nk-tab-bar`, `.nk-tab-bar-spacer`, `.always`, `.fixed`, `.floating`
1226
+
1227
+ ```html
1228
+ <!-- equivalent class markup -->
1229
+ <div style="max-width:390px;border:1px solid var(--nk-border);border-radius:12px;overflow:hidden"><nav class="nk-tab-bar always">
1230
+ <button class="nk-tab-bar-item"><span class="icon">🏠</span><span class="label">Home</span></button>
1231
+ <button class="nk-tab-bar-item active"><span class="icon">📥</span><span class="label">Inbox</span></button>
1232
+ <button class="nk-tab-bar-item"><span class="icon">🔍</span><span class="label">Search</span></button>
1233
+ <button class="nk-tab-bar-item"><span class="icon">⚙️</span><span class="label">Settings</span></button>
1234
+ <button class="nk-tab-bar-item"><span class="icon">☰</span><span class="label">More</span></button>
1235
+ </nav></div>
1236
+ ```
1237
+
1238
+ **Small screens:** This is where it lives: hidden above 860px (the sidebar is the navigation there), shown below. `always` shows it at every width – previews, phone frames. The bottom padding is the larger of 6px and `env(safe-area-inset-bottom)`; in landscape the side padding grows to the left/right insets.
1239
+
1240
+ ### 3.35 `<nk-tab-bar-item>` – Tab bar item
1241
+
1242
+ One destination of `nk-tab-bar`: an icon over a short label. A tap emits `nk-select` (cancelable), then moves the bar’s `value`; with `href` it navigates afterwards. `drawer` turns it into the “More” item that opens the nearest `nk-sidebar` as a drawer and never becomes active. Standalone it toggles its own `active`.
1243
+
1244
+ ```html
1245
+ <div style="max-width:390px;border:1px solid var(--nk-border);border-radius:12px;overflow:hidden"><nk-tab-bar always><nk-tab-bar-item icon="🏠" value="home" active>Home</nk-tab-bar-item><nk-tab-bar-item icon="📥" value="inbox">Inbox</nk-tab-bar-item></nk-tab-bar></div>
1246
+ ```
1247
+
1248
+ | Attribute | Type | Default | Description |
1249
+ |---|---|---|---|
1250
+ | `icon` | string | – | Emoji or glyph (alternative: `slot="icon"`). |
1251
+ | `value` | string | – | Value (default: the label). |
1252
+ | `label` | string | – | Label text (alternative to the default slot). |
1253
+ | `href` | string | – | Navigates after `nk-select`. |
1254
+ | `active` | boolean | – | The current destination (`aria-current="page"`). |
1255
+ | `drawer` | boolean | – | Opens the sidebar drawer instead of becoming active. |
1256
+ | `disabled` | boolean | – | Not selectable. |
1257
+
1258
+ **Slots:** `(default)` – Label. · `icon` – Icon node instead of the attribute.
1259
+
1260
+ **Events:** `nk-select` `{ value, label, href, item, drawer }` – Tapped; cancelable.
1261
+
1262
+ **Methods:** `select()`, `focus()`
1263
+
1264
+ **Replaces:** `.nk-tab-bar-item`, `.icon`, `.label`, `.active`
1265
+
1266
+ ```html
1267
+ <!-- equivalent class markup -->
1268
+ <div style="max-width:390px;border:1px solid var(--nk-border);border-radius:12px;overflow:hidden"><nav class="nk-tab-bar always"><button class="nk-tab-bar-item active"><span class="icon">🏠</span><span class="label">Home</span></button><button class="nk-tab-bar-item"><span class="icon">📥</span><span class="label">Inbox</span></button></nav></div>
1269
+ ```
1270
+
1271
+ **Small screens:** Made for the thumb: 20px icon, 10.5px label, the whole column is the hit area.
1272
+
1273
+ ## Page shell & blocks (wave 3)
1274
+
1275
+ ### 3.36 `<nk-page>` – Page
1276
+
1277
+ The document column: a scrolling wrapper, an optional cover, the 760px page with 64px side padding, and the page icon (rendered here because its slotted twin is keyed on the parent). `narrow` drops the scroll wrapper for pages that are the document itself.
1278
+
1279
+ ```html
1280
+ <div style="display:flex;flex-direction:column;height:100%"><nk-page icon="🚀" cover>
1281
+ <nk-page-title>NotionKit MVP</nk-page-title>
1282
+ <nk-page-actions><span>👤 Marcel Karas</span><span>📅 Created 12 May 2026</span><span>🏷️ <nk-tag color="purple">Design system</nk-tag></span></nk-page-actions>
1283
+ <p class="lead">A calm, document-centric workspace app – built from elements only.</p>
1284
+ </nk-page></div>
1285
+ ```
1286
+
1287
+ | Attribute | Type | Default | Description |
1288
+ |---|---|---|---|
1289
+ | `icon` | string | – | Page emoji; click fires `nk-action`. |
1290
+ | `full` | boolean | – | Page option “Full width”: the column fills the window instead of stopping at 760px. |
1291
+ | `small` | boolean | – | Page option “Small text”: document text 14px instead of 16px; headings, lead, prose and editor follow. |
1292
+ | `cover` | boolean | – | Shows the cover strip; the icon then overlaps its bottom edge and the page drops its top padding (the stylesheet’s `covered` state, also set for a slotted `nk-page-cover`). Without it the icon sits in 24px top padding, fully visible. |
1293
+ | `narrow` | boolean | – | No scroll wrapper (landing / docs page). |
1294
+
1295
+ **Slots:** `(default)` – Title, meta, blocks – anything with `class="lead"` on a `<p>` becomes the lead paragraph. · `cover` – An `nk-page-cover` (instead of the `cover` attribute). · `icon` – Custom icon node.
1296
+
1297
+ **Events:** `nk-action` `{ action: 'icon', value }` – Icon clicked (open an emoji picker).
1298
+
1299
+ **Replaces:** `.nk-page-scroll`, `.nk-page`, `.nk-page-icon`, `.nk-cover`, `.lead`, `.full`, `.small`
1300
+
1301
+ ```html
1302
+ <!-- equivalent class markup -->
1303
+ <div style="display:flex;flex-direction:column;height:100%"><div class="nk-page-scroll"><div class="nk-cover"></div><div class="nk-page">
1304
+ <div class="nk-page-icon">🚀</div>
1305
+ <h1 class="nk-page-title">NotionKit MVP</h1>
1306
+ <div class="nk-page-meta"><span>👤 Marcel Karas</span><span>📅 Created 12 May 2026</span><span>🏷️ <span class="nk-tag purple">Design system</span></span></div>
1307
+ <p class="lead">A calm, document-centric workspace app – built from elements only.</p>
1308
+ </div></div></div>
1309
+ ```
1310
+
1311
+ **Small screens:** Side padding drops to 24px below 860px.
1312
+
1313
+ ### 3.37 `<nk-page-cover>` – Page cover
1314
+
1315
+ The 200px cover band. Without `src` it shows the token gradient; with `src` a picture – an `<img>` inside the band, cropped rather than stretched. `position` moves the crop, as Notion’s “Reposition” does.
1316
+
1317
+ ```html
1318
+ <nk-page-cover src="/covers/meadow.svg"></nk-page-cover>
1319
+ ```
1320
+
1321
+ | Attribute | Type | Default | Description |
1322
+ |---|---|---|---|
1323
+ | `src` | URL | – | Cover picture. |
1324
+ | `position` | CSS object-position | `center` | Where the crop sits. |
1325
+
1326
+
1327
+
1328
+ **Replaces:** `.nk-cover`
1329
+
1330
+ ```html
1331
+ <!-- equivalent class markup -->
1332
+ <div class="nk-cover"><img src="/covers/meadow.svg" alt=""></div>
1333
+ ```
1334
+
1335
+ **Small screens:** Unchanged.
1336
+
1337
+ ### 3.38 `<nk-page-title>` – Page title
1338
+
1339
+ The 40px heading. With `editable` it becomes a plain-text field: Enter commits, blur fires `nk-change`.
1340
+
1341
+ ```html
1342
+ <nk-page-title editable>NotionKit MVP</nk-page-title>
1343
+ ```
1344
+
1345
+ | Attribute | Type | Default | Description |
1346
+ |---|---|---|---|
1347
+ | `editable` | boolean | – | Inline editing. |
1348
+ | `placeholder` | string | – | Shown when empty (editable). |
1349
+ | `value` | string | – | Title text (alternative to content). |
1350
+
1351
+ **Slots:** `(default)` – Title text.
1352
+
1353
+ **Events:** `nk-change` `{ value }` – Edited title committed.
1354
+
1355
+ **Properties:** `value`
1356
+
1357
+ **Replaces:** `.nk-page-title`
1358
+
1359
+ ```html
1360
+ <!-- equivalent class markup -->
1361
+ <h1 class="nk-page-title" contenteditable="plaintext-only" spellcheck="false">NotionKit MVP</h1>
1362
+ ```
1363
+
1364
+ **Small screens:** Unchanged; long titles wrap.
1365
+
1366
+ ### 3.39 `<nk-page-actions>` – Page meta row
1367
+
1368
+ The quiet row under the title: owner, date, tags – any inline content, 16px apart.
1369
+
1370
+ ```html
1371
+ <nk-page-actions><span>👤 Marcel Karas</span><span>📅 Created 12 May 2026</span><span>🏷️ <nk-tag color="purple">Design system</nk-tag></span></nk-page-actions>
1372
+ ```
1373
+
1374
+ _No attributes._
1375
+
1376
+ **Slots:** `(default)` – Meta items.
1377
+
1378
+ **Replaces:** `.nk-page-meta`
1379
+
1380
+ ```html
1381
+ <!-- equivalent class markup -->
1382
+ <div class="nk-page-meta"><span>👤 Marcel Karas</span><span>📅 Created 12 May 2026</span><span>🏷️ <span class="nk-tag purple">Design system</span></span></div>
1383
+ ```
1384
+
1385
+ **Small screens:** Wraps naturally.
1386
+
1387
+ ### 3.40 `<nk-props>` – Page properties
1388
+
1389
+ The properties under the title of a database page, the pattern Notion is known for: a list of `nk-prop` rows. The rows are hosts with `display: contents`, so each renders as a row of this list – no markup of their own around them.
1390
+
1391
+ ```html
1392
+ <nk-props style="max-width:520px">
1393
+ <nk-prop label="Status" icon="◉"><nk-tag color="blue">In progress</nk-tag></nk-prop>
1394
+ <nk-prop label="Owner" icon="👤"><nk-avatar size="small" color="purple">AL</nk-avatar>Ada Lovelace</nk-prop>
1395
+ <nk-prop label="Due" icon="📅">2 June 2026</nk-prop>
1396
+ <nk-prop label="Tags" icon="🏷️"><nk-tag color="purple">Design system</nk-tag><nk-tag>CSS</nk-tag></nk-prop>
1397
+ <nk-prop label="Progress" icon="▰"><nk-progress value="65" label="65 %" wide></nk-progress></nk-prop>
1398
+ </nk-props>
1399
+ ```
1400
+
1401
+ _No attributes._
1402
+
1403
+ **Slots:** `(default)` – `nk-prop` children.
1404
+
1405
+ **Replaces:** `.nk-props`
1406
+
1407
+ ```html
1408
+ <!-- equivalent class markup -->
1409
+ <dl class="nk-props" style="max-width:520px">
1410
+ <div class="nk-prop"><dt class="p-name"><span class="p-icon">◉</span>Status</dt><dd class="p-value"><span class="nk-tag blue">In progress</span></dd></div>
1411
+ <div class="nk-prop"><dt class="p-name"><span class="p-icon">👤</span>Owner</dt><dd class="p-value"><span class="nk-avatar small purple">AL</span>Ada Lovelace</dd></div>
1412
+ <div class="nk-prop"><dt class="p-name"><span class="p-icon">📅</span>Due</dt><dd class="p-value">2 June 2026</dd></div>
1413
+ <div class="nk-prop"><dt class="p-name"><span class="p-icon">🏷️</span>Tags</dt><dd class="p-value"><span class="nk-tag purple">Design system</span><span class="nk-tag">CSS</span></dd></div>
1414
+ <div class="nk-prop"><dt class="p-name"><span class="p-icon">▰</span>Progress</dt><dd class="p-value"><span class="nk-progress wide"><i style="width:65%"></i></span><span class="nk-progress-label">65 %</span></dd></div>
1415
+ </dl>
1416
+ ```
1417
+
1418
+ **Small screens:** Below 860px each property stacks: the name above its value.
1419
+
1420
+ ### 3.41 `<nk-prop>` – Page property
1421
+
1422
+ One property: the name with its type icon in a 160px column, the value beside it, 34px with the hover wash on both halves. The value is the element’s content – tags, an avatar and a name, a date, `<nk-progress wide>`. A click fires `nk-action` with the half that was hit, the moment Notion opens the property’s editor.
1423
+
1424
+ ```html
1425
+ <nk-props style="max-width:520px"><nk-prop label="Due" icon="📅">2 June 2026</nk-prop></nk-props>
1426
+ ```
1427
+
1428
+ | Attribute | Type | Default | Description |
1429
+ |---|---|---|---|
1430
+ | `label` | string | – | Property name. |
1431
+ | `icon` | string | – | Type icon. |
1432
+
1433
+ **Slots:** `(default)` – The value. · `icon` – Icon node.
1434
+
1435
+ **Events:** `nk-action` `{ action: 'name' | 'value', label }` – Name or value clicked.
1436
+
1437
+ **Replaces:** `.nk-prop`, `.p-name`, `.p-icon`, `.p-value`
1438
+
1439
+ ```html
1440
+ <!-- equivalent class markup -->
1441
+ <dl class="nk-props" style="max-width:520px"><div class="nk-prop"><dt class="p-name"><span class="p-icon">📅</span>Due</dt><dd class="p-value">2 June 2026</dd></div></dl>
1442
+ ```
1443
+
1444
+ **Small screens:** Stacks below 860px.
1445
+
1446
+ ### 3.42 `<nk-panels>` – Panels
1447
+
1448
+ A grid of `nk-panel`s: columns of at least 200px that share the row. The panels are hosts with `display: contents`, so each is a cell of this grid.
1449
+
1450
+ ```html
1451
+ <nk-panels>
1452
+ <nk-panel href="#" cover="/covers/aurora.svg" icon="🚀" title="NotionKit MVP"><p>2 min ago</p></nk-panel>
1453
+ <nk-panel href="#" cover="/covers/dunes.svg" icon="📚" title="Knowledge base"><p>Yesterday</p></nk-panel>
1454
+ <nk-panel title="Weekly review"><p>Three pages changed, one comment is waiting for an answer.</p></nk-panel>
1455
+ </nk-panels>
1456
+ ```
1457
+
1458
+ _No attributes._
1459
+
1460
+ **Slots:** `(default)` – `nk-panel` children.
1461
+
1462
+ **Replaces:** `.nk-panels`
1463
+
1464
+ ```html
1465
+ <!-- equivalent class markup -->
1466
+ <div class="nk-panels">
1467
+ <a class="nk-panel" href="#"><div class="nk-cover"><img src="/covers/aurora.svg" alt=""></div><div class="nk-page-icon">🚀</div><h3>NotionKit MVP</h3><p>2 min ago</p></a>
1468
+ <a class="nk-panel" href="#"><div class="nk-cover"><img src="/covers/dunes.svg" alt=""></div><div class="nk-page-icon">📚</div><h3>Knowledge base</h3><p>Yesterday</p></a>
1469
+ <div class="nk-panel"><h3>Weekly review</h3><p>Three pages changed, one comment is waiting for an answer.</p></div>
1470
+ </div>
1471
+ ```
1472
+
1473
+ **Small screens:** Falls to one column as soon as two 200px columns no longer fit.
1474
+
1475
+ ### 3.43 `<nk-panel>` – Panel
1476
+
1477
+ A neutral surface for content that belongs together – the cards on Notion’s Home, the boxes in its settings. `title` is the heading (never a tooltip); the content goes in as `<p>`s and blocks. `cover` without a value draws the gradient band, with a URL a picture; `icon` overlaps the cover as on a page – a page tile. With `href` the panel is a link.
1478
+
1479
+ ```html
1480
+ <div style="max-width:260px"><nk-panel href="#" cover icon="🚀" title="NotionKit MVP"><p>2 min ago</p></nk-panel></div>
1481
+ ```
1482
+
1483
+ | Attribute | Type | Default | Description |
1484
+ |---|---|---|---|
1485
+ | `title` | string | – | Heading. |
1486
+ | `icon` | string | – | Page icon. |
1487
+ | `cover` | URL | empty | – | Cover band: empty for the gradient, a URL for a picture. |
1488
+ | `href` | URL | – | Makes the panel a link. |
1489
+ | `target` | string | – | Link target. |
1490
+
1491
+ **Slots:** `(default)` – Content: `<p>`, a prose block, a progress bar.
1492
+
1493
+ **Replaces:** `.nk-panel`, `.nk-cover`, `.nk-page-icon`
1494
+
1495
+ ```html
1496
+ <!-- equivalent class markup -->
1497
+ <div style="max-width:260px"><a class="nk-panel" href="#"><div class="nk-cover"></div><div class="nk-page-icon">🚀</div><h3>NotionKit MVP</h3><p>2 min ago</p></a></div>
1498
+ ```
1499
+
1500
+ **Small screens:** Takes the width of its grid cell.
1501
+
1502
+ ### 3.44 `<nk-block-host>` – Block host
1503
+
1504
+ The optical shell for editor content: hover wash, focus ring, drop-target line, an optional drag handle. It stays behaviour-neutral – mount your editor into the light DOM; `nk-editor` (v1.1) will do that for TipTap.
1505
+
1506
+ ```html
1507
+ <nk-block-host handle><p style="margin:0" contenteditable="true">Block content lives here – click to focus.</p></nk-block-host>
1508
+ ```
1509
+
1510
+ | Attribute | Type | Default | Description |
1511
+ |---|---|---|---|
1512
+ | `handle` | boolean | – | Render the ⠿ drag handle (shown on hover). |
1513
+ | `drop-target` | boolean | – | Drop indicator line above the block. |
1514
+
1515
+ **Slots:** `(default)` – Block content / the editor root.
1516
+
1517
+ **Replaces:** `.nk-block-host`, `.nk-block-handle`, `.nk-drop-target`
1518
+
1519
+ ```html
1520
+ <!-- equivalent class markup -->
1521
+ <div class="nk-block-host"><span class="nk-block-handle">⠿</span><p style="margin:0" contenteditable="true">Block content lives here – click to focus.</p></div>
1522
+ ```
1523
+
1524
+ **Small screens:** The handle sits 26px left of the column and is hidden when there is no room.
1525
+
1526
+ ### 3.45 `<nk-banner>` – Banner
1527
+
1528
+ A tinted notice row. The colour modifier becomes `variant`; the action – a `<button>` – goes into `slot="action"` and sits at the right edge.
1529
+
1530
+ ```html
1531
+ <nk-banner variant="info">ℹ️ <span>This page is a <b>component preview</b> – every element follows the same design tokens.</span><button slot="action" type="button">Open palette</button></nk-banner>
1532
+ <nk-banner variant="warning">⚠️ <span>The “Project overview” database has 2 overdue entries.</span><button slot="action" type="button">View</button></nk-banner>
1533
+ <nk-banner variant="success">✓ <span>All changes have been synced.</span></nk-banner>
1534
+ <nk-banner variant="danger">⛔ <span>The connection to Notion was lost.</span></nk-banner>
1535
+ ```
1536
+
1537
+ | Attribute | Type | Default | Description |
1538
+ |---|---|---|---|
1539
+ | `variant` | info | success | warning | danger | – | Tint. |
1540
+
1541
+ **Slots:** `(default)` – Icon and text. · `action` – The action – a `<button>`, shown as underlined text on the right.
1542
+
1543
+ **Replaces:** `.nk-banner`, `.info`, `.success`, `.warning`, `.danger`, `.b-action`
1544
+
1545
+ ```html
1546
+ <!-- equivalent class markup -->
1547
+ <div class="nk-banner info">ℹ️ <span>This page is a <b>component preview</b> – every element follows the same design tokens.</span><button class="b-action" type="button">Open palette</button></div>
1548
+ <div class="nk-banner warning">⚠️ <span>The “Project overview” database has 2 overdue entries.</span><button class="b-action" type="button">View</button></div>
1549
+ <div class="nk-banner success">✓ <span>All changes have been synced.</span></div>
1550
+ <div class="nk-banner danger">⛔ <span>The connection to Notion was lost.</span></div>
1551
+ ```
1552
+
1553
+ **Small screens:** Below 860px the action moves under the text instead of squeezing it into a narrow column.
1554
+
1555
+ ### 3.46 `<nk-empty>` – Empty state
1556
+
1557
+ Dashed box with icon, title, description and whatever call to action you slot in.
1558
+
1559
+ ```html
1560
+ <nk-empty icon="🗂️" title="No entries yet" desc="Create the first entry or import existing data."><nk-btn variant="primary" small>+ New entry</nk-btn><nk-btn variant="secondary" small>Import</nk-btn></nk-empty>
1561
+ ```
1562
+
1563
+ | Attribute | Type | Default | Description |
1564
+ |---|---|---|---|
1565
+ | `icon` | string | – | Emoji. |
1566
+ | `title` | string | – | Title. Read once and taken off the host, so it never shows as a tooltip. |
1567
+ | `desc` | string | – | Description. |
1568
+
1569
+ **Slots:** `(default)` – Call to action – several buttons sit in a centred row, 8px apart. · `icon` – Rich icon. · `title` – Rich title. · `desc` – Rich description.
1570
+
1571
+ **Replaces:** `.nk-empty`, `.e-icon`, `.e-title`, `.e-desc`, `.e-actions`
1572
+
1573
+ ```html
1574
+ <!-- equivalent class markup -->
1575
+ <div class="nk-empty"><div class="e-icon">🗂️</div><div class="e-title">No entries yet</div><div class="e-desc">Create the first entry or import existing data.</div><div class="e-actions"><button class="nk-btn primary small">+ New entry</button><button class="nk-btn secondary small">Import</button></div></div>
1576
+ ```
1577
+
1578
+ **Small screens:** Unchanged.
1579
+
1580
+ ### 3.47 `<nk-skeleton>` – Skeleton
1581
+
1582
+ Shimmering placeholder lines. `lines` renders several; `widths` gives each its own width.
1583
+
1584
+ ```html
1585
+ <nk-skeleton height="18" width="60%"></nk-skeleton>
1586
+ <nk-skeleton lines="3" widths="100%,85%,40%"></nk-skeleton>
1587
+ ```
1588
+
1589
+ | Attribute | Type | Default | Description |
1590
+ |---|---|---|---|
1591
+ | `lines` | number | `1` | Number of lines. |
1592
+ | `height` | px | CSS length | `13` | Line height. |
1593
+ | `width` | CSS length | – | Width for every line. |
1594
+ | `widths` | list | – | Comma-separated width per line. |
1595
+
1596
+
1597
+
1598
+ **Replaces:** `.nk-skeleton`
1599
+
1600
+ ```html
1601
+ <!-- equivalent class markup -->
1602
+ <div class="nk-skeleton" style="height:18px;width:60%"></div>
1603
+ <div class="nk-skeleton" style="height:13px"></div>
1604
+ <div class="nk-skeleton" style="height:13px;width:85%"></div>
1605
+ <div class="nk-skeleton" style="height:13px;width:40%"></div>
1606
+ ```
1607
+
1608
+ **Small screens:** Unchanged; respects reduced motion.
1609
+
1610
+ ### 3.48 `<nk-synced>` – Synced block
1611
+
1612
+ Content that appears in several places, framed with a badge.
1613
+
1614
+ ```html
1615
+ <nk-synced badge="⟳ 3 places"><div style="font-size:14px;line-height:1.55"><b>Our mission:</b> Take real weight off the working day – calm, clear, effective.</div></nk-synced>
1616
+ ```
1617
+
1618
+ | Attribute | Type | Default | Description |
1619
+ |---|---|---|---|
1620
+ | `badge` | string | `⟳ synced` | Badge text. |
1621
+
1622
+ **Slots:** `(default)` – Content.
1623
+
1624
+ **Replaces:** `.nk-synced`, `.synced-badge`
1625
+
1626
+ ```html
1627
+ <!-- equivalent class markup -->
1628
+ <div class="nk-synced"><span class="synced-badge">⟳ 3 places</span><div style="font-size:14px;line-height:1.55"><b>Our mission:</b> Take real weight off the working day – calm, clear, effective.</div></div>
1629
+ ```
1630
+
1631
+ **Small screens:** Unchanged.
1632
+
1633
+ ### 3.49 `<nk-tabs>` – Tabs
1634
+
1635
+ A tab strip with panels. `nk-tab` children are the tabs; elements with `slot="panel"` and a matching `data-tab` are the panels – the tabs hide every panel but the active one through `hidden`. Arrow keys move between tabs.
1636
+
1637
+ ```html
1638
+ <nk-tabs value="notes">
1639
+ <nk-tab value="notes">📝 Notes</nk-tab>
1640
+ <nk-tab value="tasks">✅ Tasks</nk-tab>
1641
+ <nk-tab value="files">📎 Files</nk-tab>
1642
+ <div slot="panel" data-tab="notes" class="nk-tab-panel">Free-form notes on the project – meeting minutes, ideas, rough drafts.</div>
1643
+ <div slot="panel" data-tab="tasks" class="nk-tab-panel">Tasks for this project, linked to the database below.</div>
1644
+ <div slot="panel" data-tab="files" class="nk-tab-panel">Attached files and exports.</div>
1645
+ </nk-tabs>
1646
+ ```
1647
+
1648
+ | Attribute | Type | Default | Description |
1649
+ |---|---|---|---|
1650
+ | `value` | string | – | Active tab value (default: the tab with `active`, else the first). |
1651
+
1652
+ **Slots:** `(default)` – `nk-tab` children. · `panel` – Panels with `data-tab`.
1653
+
1654
+ **Events:** `nk-change` `{ value }` – Active tab changed. · `nk-select` `{ value, label }` – From the clicked tab.
1655
+
1656
+ **Properties:** `value`
1657
+
1658
+ **Replaces:** `.nk-tabs`, `.nk-tab`, `.active`, `.nk-tab-panel`
1659
+
1660
+ ```html
1661
+ <!-- equivalent class markup -->
1662
+ <div class="nk-tabs">
1663
+ <span class="nk-tab active">📝 Notes</span>
1664
+ <span class="nk-tab">✅ Tasks</span>
1665
+ <span class="nk-tab">📎 Files</span>
1666
+ </div>
1667
+ <div class="nk-tab-panel">Free-form notes on the project – meeting minutes, ideas, rough drafts.</div>
1668
+ ```
1669
+
1670
+ **Small screens:** Strip stays on one line; keep labels short.
1671
+
1672
+ ### 3.50 `<nk-tab>` – Tab
1673
+
1674
+ One tab of `nk-tabs`. Standalone it toggles its own `active`.
1675
+
1676
+ ```html
1677
+ <nk-tabs><nk-tab value="a" active>📝 Notes</nk-tab><nk-tab value="b">✅ Tasks</nk-tab></nk-tabs>
1678
+ ```
1679
+
1680
+ | Attribute | Type | Default | Description |
1681
+ |---|---|---|---|
1682
+ | `value` | string | – | Value (default: text). |
1683
+ | `active` | boolean | – | Active. |
1684
+ | `disabled` | boolean | – | Not selectable. |
1685
+
1686
+ **Slots:** `(default)` – Label.
1687
+
1688
+ **Events:** `nk-select` `{ value, label }` – Clicked / Enter.
1689
+
1690
+ **Replaces:** `.nk-tab`, `.active`
1691
+
1692
+ ```html
1693
+ <!-- equivalent class markup -->
1694
+ <div class="nk-tabs"><span class="nk-tab active">📝 Notes</span><span class="nk-tab">✅ Tasks</span></div>
1695
+ ```
1696
+
1697
+ **Small screens:** Unchanged.
1698
+
1699
+ ### 3.51 `<nk-segmented>` – Segmented control
1700
+
1701
+ Plain `<button value>` children stay in the light DOM (the stylesheet’s slotted twins shape them); the element moves `.active`, handles arrow keys and submits `value` with the form. With `scroll` the chosen option stays in view: after the first layout, on every change and when the row’s width changes, the row – never the page – scrolls the least distance, right to left as well.
1702
+
1703
+ ```html
1704
+ <nk-segmented name="range" value="week"><button value="week">Week</button><button value="month">Month</button><button value="quarter">Quarter</button></nk-segmented>
1705
+ <div style="max-width:300px;margin-top:12px"><nk-segmented name="filter" value="all" scroll><button value="all">All</button><button value="attention">⚠️ Attention</button><button value="failed">Failed</button><button value="read">Read</button><button value="ignored">Ignored</button></nk-segmented></div>
1706
+ <div style="max-width:300px;margin-top:12px"><nk-segmented name="filter2" value="all" wrap><button value="all">All</button><button value="attention">⚠️ Attention</button><button value="failed">Failed</button><button value="read">Read</button><button value="ignored">Ignored</button></nk-segmented></div>
1707
+ ```
1708
+
1709
+ | Attribute | Type | Default | Description |
1710
+ |---|---|---|---|
1711
+ | `value` | string | – | Selected value (default: the button with `.active`, else the first). |
1712
+ | `scroll` | boolean | – | Horizontally scrollable row, scrollbar hidden. |
1713
+ | `wrap` | boolean | – | Segments wrap onto further rows. |
1714
+ | `name` | string | – | Form field name (FormData key). |
1715
+ | `disabled` | boolean | – | Disables the control. |
1716
+
1717
+ **Slots:** `(default)` – `<button value="…">` children.
1718
+
1719
+ **Events:** `nk-change` `{ value, name }` – Selection changed.
1720
+
1721
+ **Replaces:** `.nk-segmented`, `.active`, `.scroll`, `.wrap`
1722
+
1723
+ ```html
1724
+ <!-- equivalent class markup -->
1725
+ <div class="nk-segmented"><button class="active">Week</button><button>Month</button><button>Quarter</button></div>
1726
+ <div style="max-width:300px;margin-top:12px"><div class="nk-segmented scroll"><button class="active">All</button><button>⚠️ Attention</button><button>Failed</button><button>Read</button><button>Ignored</button></div></div>
1727
+ <div style="max-width:300px;margin-top:12px"><div class="nk-segmented wrap"><button class="active">All</button><button>⚠️ Attention</button><button>Failed</button><button>Read</button><button>Ignored</button></div></div>
1728
+ ```
1729
+
1730
+ **Small screens:** One row by default, which five filter options overflow on a phone: `scroll` keeps one thumb-swipeable row capped at the parent width (scrollbar hidden), `wrap` breaks it onto further rows.
1731
+
1732
+ ### 3.52 `<nk-steps>` – Steps
1733
+
1734
+ A short flow – connecting an account, setting up a model – calm and vertical: a numbered circle per step joined by a hairline, done steps with a check on the green tag, the current one ringed in the accent and marked `aria-current="step"`. `current` counts from 1; one past the last marks every step done, and `next()` moves on. The `steps` attribute is a comma-separated list; the property also takes `{ label, desc }` objects for a line under the label.
1735
+
1736
+ ```html
1737
+ <nk-steps label="Connect your own model" current="2"></nk-steps>
1738
+ <script>{ document.currentScript.previousElementSibling.steps = [{ label: 'Choose a provider', desc: 'Anthropic' }, 'Enter the API key', 'Test the connection']; }</script>
1739
+ ```
1740
+
1741
+ | Attribute | Type | Default | Description |
1742
+ |---|---|---|---|
1743
+ | `steps` | list | – | Comma-separated step labels. |
1744
+ | `current` | number | `1` | The current step, from 1. |
1745
+ | `label` | string | – | The list’s accessible name. |
1746
+
1747
+ **Properties:** `steps`, `current` · **Methods:** `next()`
1748
+
1749
+ **Replaces:** `.nk-steps`, `.nk-step`, `.st-mark`, `.st-desc`, `.done`, `.current`
1750
+
1751
+ ```html
1752
+ <!-- equivalent class markup -->
1753
+ <ol class="nk-steps" aria-label="Connect your own model">
1754
+ <li class="nk-step done"><span class="st-mark">✓</span><span>Choose a provider<span class="st-desc">Anthropic</span></span></li>
1755
+ <li class="nk-step current" aria-current="step"><span class="st-mark">2</span><span>Enter the API key</span></li>
1756
+ <li class="nk-step"><span class="st-mark">3</span><span>Test the connection</span></li>
1757
+ </ol>
1758
+ ```
1759
+
1760
+ **Small screens:** Unchanged: vertical, so it never runs out of width.
1761
+
1762
+ ### 3.53 `<nk-stats>` – Stat cards
1763
+
1764
+ `nk-stats` is the row; each `nk-stat` shows label, value and a trend line coloured by `trend`.
1765
+
1766
+ ```html
1767
+ <nk-stats>
1768
+ <nk-stat label="Active pages" value="128" delta="▲ 12 this week" trend="up"></nk-stat>
1769
+ <nk-stat label="AI requests" value="847" delta="▲ 23 %" trend="up"></nk-stat>
1770
+ <nk-stat label="Open tasks" value="14" delta="▼ 5 since yesterday" trend="down"></nk-stat>
1771
+ </nk-stats>
1772
+ ```
1773
+
1774
+ | Attribute | Type | Default | Description |
1775
+ |---|---|---|---|
1776
+ | `label` | string | – | (nk-stat) Label. |
1777
+ | `value` | string | – | (nk-stat) Big number. |
1778
+ | `delta` | string | – | (nk-stat) Trend text. |
1779
+ | `trend` | up | down | – | (nk-stat) Colours the delta. |
1780
+
1781
+ **Slots:** `(default)` – (nk-stats) `nk-stat` children; (nk-stat) slots `label`, `value`, `delta` for rich content.
1782
+
1783
+ **Replaces:** `.nk-stats`, `.nk-stat`, `.s-label`, `.s-value`, `.s-delta`, `.up`, `.down`
1784
+
1785
+ ```html
1786
+ <!-- equivalent class markup -->
1787
+ <div class="nk-stats">
1788
+ <div class="nk-stat"><div class="s-label">Active pages</div><div class="s-value">128</div><div class="s-delta up">▲ 12 this week</div></div>
1789
+ <div class="nk-stat"><div class="s-label">AI requests</div><div class="s-value">847</div><div class="s-delta up">▲ 23 %</div></div>
1790
+ <div class="nk-stat"><div class="s-label">Open tasks</div><div class="s-value">14</div><div class="s-delta down">▼ 5 since yesterday</div></div>
1791
+ </div>
1792
+ ```
1793
+
1794
+ **Small screens:** The row wraps below 860px.
1795
+
1796
+ ### 3.54 `<nk-stat>` – Stat card
1797
+
1798
+ One card; see `nk-stats` for the row.
1799
+
1800
+ ```html
1801
+ <nk-stats><nk-stat label="Active pages" value="128" delta="▲ 12 this week" trend="up"></nk-stat></nk-stats>
1802
+ ```
1803
+
1804
+ | Attribute | Type | Default | Description |
1805
+ |---|---|---|---|
1806
+ | `label` | string | – | Label. |
1807
+ | `value` | string | – | Value. |
1808
+ | `delta` | string | – | Trend text. |
1809
+ | `trend` | up | down | – | Delta colour. |
1810
+
1811
+ **Slots:** `label` – Rich label. · `value` – Rich value. · `delta` – Rich delta (add `class="up"` / `"down"`).
1812
+
1813
+ **Replaces:** `.nk-stat`
1814
+
1815
+ ```html
1816
+ <!-- equivalent class markup -->
1817
+ <div class="nk-stats"><div class="nk-stat"><div class="s-label">Active pages</div><div class="s-value">128</div><div class="s-delta up">▲ 12 this week</div></div></div>
1818
+ ```
1819
+
1820
+ **Small screens:** Unchanged.
1821
+
1822
+ ### 3.55 `<nk-avatar-group>` – Avatar group
1823
+
1824
+ Overlapping `.mini-avatar` children (light DOM, styled by the slotted twins) plus a “more” bubble from the attribute. Pass `.mini-avatar`, not `.nk-avatar`: the document rule of `.nk-avatar` sets its own 24px, and for slotted nodes the document wins over the group’s 26px.
1825
+
1826
+ ```html
1827
+ <div style="display:flex;align-items:center;gap:12px"><nk-avatar-group more="+2"><span class="mini-avatar" style="background:linear-gradient(135deg,var(--nk-decor-purple),var(--nk-decor-blue))">MK</span><span class="mini-avatar" style="background:var(--nk-color-green)">SL</span><span class="mini-avatar" style="background:var(--nk-color-orange)">TW</span></nk-avatar-group><span style="font-size:12.5px;color:var(--nk-text-tertiary)">5 people have access</span></div>
1828
+ ```
1829
+
1830
+ | Attribute | Type | Default | Description |
1831
+ |---|---|---|---|
1832
+ | `more` | string | – | Text of the trailing bubble, e.g. `+2`. |
1833
+
1834
+ **Slots:** `(default)` – `<span class="mini-avatar" style="background:…">` children.
1835
+
1836
+ **Replaces:** `.nk-avatar-group`, `.mini-avatar`, `.more`
1837
+
1838
+ ```html
1839
+ <!-- equivalent class markup -->
1840
+ <div style="display:flex;align-items:center;gap:12px"><div class="nk-avatar-group"><span class="nk-avatar">MK</span><span class="nk-avatar green">SL</span><span class="nk-avatar orange">TW</span><span class="mini-avatar more">+2</span></div><span style="font-size:12.5px;color:var(--nk-text-tertiary)">5 people have access</span></div>
1841
+ ```
1842
+
1843
+ **Small screens:** Unchanged.
1844
+
1845
+ ### 3.56 `<nk-avatar>` – Avatar
1846
+
1847
+ A person or a workspace: initials, an emoji or a photo in a circle. `size` small (20px), default 24px, large (32px), xlarge (56px); `color` one of Notion’s nine names or any CSS background, without it the avatar gradient; `square` for a workspace icon. Without content the initials come from `name`; with `src` a photo fills the circle and `name` becomes its alt text. Initials from `name` are the first letter or digit of each word: “Planer (Dev)” → PD, “Anna-Lena Groß” → AG.
1848
+
1849
+ ```html
1850
+ <div style="display:flex;align-items:center;gap:10px"><nk-avatar size="small" color="blue">TW</nk-avatar><nk-avatar>AL</nk-avatar><nk-avatar color="green">SL</nk-avatar><nk-avatar size="large" color="orange">MK</nk-avatar><nk-avatar size="xlarge" color="purple" name="Ada Lovelace"></nk-avatar><nk-avatar size="large" square>A</nk-avatar></div>
1851
+ ```
1852
+
1853
+ | Attribute | Type | Default | Description |
1854
+ |---|---|---|---|
1855
+ | `size` | small | large | xlarge | – | Size; default 24px. |
1856
+ | `color` | colour name | CSS | – | gray, brown, orange, yellow, green, blue, purple, pink, red – or any CSS background. |
1857
+ | `square` | boolean | – | Corners instead of a circle. |
1858
+ | `name` | string | – | Initials when empty; alt text of the photo. |
1859
+ | `src` | URL | – | Photo. |
1860
+
1861
+ **Slots:** `(default)` – Initials or an emoji.
1862
+
1863
+ **Replaces:** `.nk-avatar`, `.small`, `.large`, `.xlarge`, `.square`, `.green`, `.blue`, `.orange`, `.purple`
1864
+
1865
+ ```html
1866
+ <!-- equivalent class markup -->
1867
+ <div style="display:flex;align-items:center;gap:10px"><span class="nk-avatar small blue">TW</span><span class="nk-avatar">AL</span><span class="nk-avatar green">SL</span><span class="nk-avatar large orange">MK</span><span class="nk-avatar xlarge purple">AL</span><span class="nk-avatar large square">A</span></div>
1868
+ ```
1869
+
1870
+ **Small screens:** Unchanged. A fixed size, so a row of avatars never reflows.
1871
+
1872
+ ### 3.57 `<nk-mention>` – Mention
1873
+
1874
+ Inline chip for a person (with avatar slot), a page or a date.
1875
+
1876
+ ```html
1877
+ <p style="margin:0;line-height:1.7"><nk-mention type="person"><span slot="avatar" class="mini-avatar" style="background:#448361">SL</span>Sara Lindt</nk-mention> · <nk-mention type="page">📄 Onboarding</nk-mention> · <nk-mention type="date">📅 20 May</nk-mention></p>
1878
+ ```
1879
+
1880
+ | Attribute | Type | Default | Description |
1881
+ |---|---|---|---|
1882
+ | `type` | person | page | date | – | Kind of mention. |
1883
+
1884
+ **Slots:** `avatar` – `.mini-avatar` for persons. · `(default)` – Text.
1885
+
1886
+ **Replaces:** `.nk-mention`, `.person`, `.page`, `.date`, `.mini-avatar`
1887
+
1888
+ ```html
1889
+ <!-- equivalent class markup -->
1890
+ <p style="margin:0;line-height:1.7"><span class="nk-mention person"><span class="mini-avatar" style="background:#448361">SL</span>Sara Lindt</span> · <span class="nk-mention page">📄 Onboarding</span> · <span class="nk-mention date">📅 20 May</span></p>
1891
+ ```
1892
+
1893
+ **Small screens:** Unchanged; never wraps.
1894
+
1895
+ ### 3.58 `<nk-template-btn>` – Template button
1896
+
1897
+ Full-width, left-aligned button on the callout background – “insert a template”. Fires `nk-select` with `value`.
1898
+
1899
+ ```html
1900
+ <nk-template-btn icon="📅" value="week-plan">Insert week plan</nk-template-btn>
1901
+ <nk-template-btn icon="🤝" value="minutes">Insert meeting minutes</nk-template-btn>
1902
+ <nk-template-btn icon="🔁" value="retro">Insert retro board</nk-template-btn>
1903
+ ```
1904
+
1905
+ | Attribute | Type | Default | Description |
1906
+ |---|---|---|---|
1907
+ | `icon` | string | – | Leading emoji. |
1908
+ | `value` | string | – | Reported value (default: text). |
1909
+ | `disabled` | boolean | – | Disabled. |
1910
+
1911
+ **Slots:** `(default)` – Label.
1912
+
1913
+ **Events:** `nk-select` `{ value, label }` – Clicked.
1914
+
1915
+ **Replaces:** `.nk-template-btn`
1916
+
1917
+ ```html
1918
+ <!-- equivalent class markup -->
1919
+ <button class="nk-template-btn">📅 Insert week plan</button>
1920
+ <button class="nk-template-btn">🤝 Insert meeting minutes</button>
1921
+ <button class="nk-template-btn">🔁 Insert retro board</button>
1922
+ ```
1923
+
1924
+ **Small screens:** Unchanged.
1925
+
1926
+ ### 3.59 `<nk-model-card>` – Model card
1927
+
1928
+ A radio-like card. Cards with the same `name` form a group; the selected one submits `value` with the form.
1929
+
1930
+ ```html
1931
+ <nk-model-card name="model" value="pro" title="Mona Pro" desc="Best for long documents and research." selected></nk-model-card>
1932
+ <nk-model-card name="model" value="fast" title="Mona Fast" desc="Quick answers, lower cost."></nk-model-card>
1933
+ ```
1934
+
1935
+ | Attribute | Type | Default | Description |
1936
+ |---|---|---|---|
1937
+ | `title` | string | – | Name line. Read once and taken off the host, so it never shows as a tooltip. |
1938
+ | `desc` | string | – | Description. |
1939
+ | `name` | string | – | Form field name (FormData key). |
1940
+ | `disabled` | boolean | – | Disables the control. |
1941
+ | `value` | string | – | Submitted value. |
1942
+ | `selected` | boolean | – | Selected. |
1943
+
1944
+ **Slots:** `title` – Rich name line (e.g. with an `nk-tag`). · `desc` – Rich description.
1945
+
1946
+ **Events:** `nk-change` `{ value, name, checked }` – Selected. · `nk-select` `{ value, label }` – Selected.
1947
+
1948
+ **Replaces:** `.nk-model-card`, `.selected`, `.m-radio`, `.m-name`, `.m-desc`
1949
+
1950
+ ```html
1951
+ <!-- equivalent class markup -->
1952
+ <div class="nk-model-card selected"><div class="m-radio"></div><div><div class="m-name">Mona Pro</div><div class="m-desc">Best for long documents and research.</div></div></div>
1953
+ <div class="nk-model-card"><div class="m-radio"></div><div><div class="m-name">Mona Fast</div><div class="m-desc">Quick answers, lower cost.</div></div></div>
1954
+ ```
1955
+
1956
+ **Small screens:** Unchanged.
1957
+
1958
+ ### 3.60 `<nk-profile-row>` – Profile row
1959
+
1960
+ A 56px gradient avatar with whatever you slot beside it – usually two buttons.
1961
+
1962
+ ```html
1963
+ <nk-profile-row avatar="MK"><nk-btn variant="secondary" small>Change photo</nk-btn> <nk-btn variant="danger" small>Remove</nk-btn></nk-profile-row>
1964
+ ```
1965
+
1966
+ | Attribute | Type | Default | Description |
1967
+ |---|---|---|---|
1968
+ | `avatar` | string | – | Initials. |
1969
+
1970
+ **Slots:** `avatar` – Custom avatar (e.g. an image). · `(default)` – Content beside the avatar.
1971
+
1972
+ **Replaces:** `.nk-profile-row`, `.big-avatar`
1973
+
1974
+ ```html
1975
+ <!-- equivalent class markup -->
1976
+ <div class="nk-profile-row"><div class="big-avatar">MK</div><button class="nk-btn secondary small">Change photo</button> <button class="nk-btn danger small">Remove</button></div>
1977
+ ```
1978
+
1979
+ **Small screens:** Unchanged.
1980
+
1981
+ ### 3.61 `<nk-danger-zone>` – Danger zone
1982
+
1983
+ Red-framed box for destructive settings.
1984
+
1985
+ ```html
1986
+ <nk-danger-zone title="Danger zone"><nk-field label="Delete workspace" desc="Deleting the workspace removes every page."><nk-btn variant="danger-solid" small>Delete</nk-btn></nk-field></nk-danger-zone>
1987
+ ```
1988
+
1989
+ | Attribute | Type | Default | Description |
1990
+ |---|---|---|---|
1991
+ | `title` | string | – | Red heading. Read once and taken off the host, so it never shows as a tooltip. |
1992
+
1993
+ **Slots:** `(default)` – Fields and buttons.
1994
+
1995
+ **Replaces:** `.nk-danger-zone`, `.dz-title`
1996
+
1997
+ ```html
1998
+ <!-- equivalent class markup -->
1999
+ <div class="nk-danger-zone"><div class="dz-title">Danger zone</div><div class="nk-field"><div><div class="f-label">Delete workspace</div><div class="f-desc">Deleting the workspace removes every page.</div></div><div class="f-control"><button class="nk-btn danger-solid small">Delete</button></div></div></div>
2000
+ ```
2001
+
2002
+ **Small screens:** Unchanged.
2003
+
2004
+ ### 3.62 `<nk-member-list>` – Member list
2005
+
2006
+ Rows of `nk-member-row`; the list marks the last row so it loses its bottom border. Each row shows avatar (initials + `color`), name, mail and a `slot="role"` control on the right.
2007
+
2008
+ ```html
2009
+ <nk-member-list>
2010
+ <nk-member-row name="Sara Lindt" mail="sara@example.com" color="#448361"><nk-select slot="role" compact value="editor"><option value="viewer">Viewer</option><option value="editor">Editor</option><option value="admin">Admin</option></nk-select></nk-member-row>
2011
+ <nk-member-row name="Tom Weber" mail="tom@example.com" color="#d9730d"><nk-select slot="role" compact value="viewer"><option value="viewer">Viewer</option><option value="editor">Editor</option><option value="admin">Admin</option></nk-select></nk-member-row>
2012
+ </nk-member-list>
2013
+ ```
2014
+
2015
+ | Attribute | Type | Default | Description |
2016
+ |---|---|---|---|
2017
+ | `name` | string | – | (row) Name. |
2018
+ | `mail` | string | – | (row) Mail line. |
2019
+ | `avatar` | string | – | (row) Initials (default: from the name). |
2020
+ | `color` | CSS color | – | (row) Avatar background. |
2021
+ | `last` | boolean | – | (row) No bottom border – set by the list. |
2022
+
2023
+ **Slots:** `(default)` – (list) rows; (row) extra content. · `role` – (row) A control on the right, e.g. `nk-select compact`. · `avatar` – (row) Custom avatar.
2024
+
2025
+ **Replaces:** `.nk-member-list`, `.nk-member-row`, `.last`, `.m-mail`, `.mini-avatar`
2026
+
2027
+ ```html
2028
+ <!-- equivalent class markup -->
2029
+ <div class="nk-member-list">
2030
+ <div class="nk-member-row"><span class="mini-avatar" style="background:#448361">SL</span><div>Sara Lindt<div class="m-mail">sara@example.com</div></div><select class="nk-select"><option>Viewer</option><option selected>Editor</option><option>Admin</option></select></div>
2031
+ <div class="nk-member-row"><span class="mini-avatar" style="background:#d9730d">TW</span><div>Tom Weber<div class="m-mail">tom@example.com</div></div><select class="nk-select"><option selected>Viewer</option><option>Editor</option><option>Admin</option></select></div>
2032
+ </div>
2033
+ ```
2034
+
2035
+ **Small screens:** Unchanged; the role select shrinks to 120px.
2036
+
2037
+ ### 3.63 `<nk-member-row>` – Member row
2038
+
2039
+ One row; see `nk-member-list`.
2040
+
2041
+ ```html
2042
+ <nk-member-row name="Sara Lindt" mail="sara@example.com" color="green" last></nk-member-row>
2043
+ ```
2044
+
2045
+ | Attribute | Type | Default | Description |
2046
+ |---|---|---|---|
2047
+ | `name` | string | – | Name. |
2048
+ | `mail` | string | – | Mail. |
2049
+ | `avatar` | string | – | Initials. |
2050
+ | `color` | colour name | CSS | – | Avatar colour: one of the nine names (gray … red) or any CSS background; without it the avatar gradient. |
2051
+ | `last` | boolean | – | No bottom border. |
2052
+
2053
+ **Slots:** `role` – Control on the right. · `avatar` – Custom avatar. · `(default)` – Extra content.
2054
+
2055
+ **Replaces:** `.nk-member-row`
2056
+
2057
+ ```html
2058
+ <!-- equivalent class markup -->
2059
+ <div class="nk-member-row last"><span class="nk-avatar green">SL</span><div>Sara Lindt<div class="m-mail">sara@example.com</div></div></div>
2060
+ ```
2061
+
2062
+ **Small screens:** Unchanged.
2063
+
2064
+ ## Overlays (wave 4)
2065
+
2066
+ ### 3.64 `<nk-modal>` – Settings modal
2067
+
2068
+ The settings overlay: backdrop, a 960×640 dialog with a nav column and a content column. The nav rows are rendered by the modal from the panes’ `label`/`icon`/`group`, so the 27px rows and the 860px icon rail come straight from the stylesheet. Escape and the backdrop close it; focus moves in and back; the page behind is scroll-locked and inert. Place it directly under `<body>`.
2069
+
2070
+ ```html
2071
+ <nk-modal open>
2072
+ <nk-settings-user slot="user" name="Marcel Karas" mail="marcel@monahilft.de"></nk-settings-user>
2073
+ <nk-settings-pane name="profile" group="Account" icon="👤" label="My profile" title="My profile" active>
2074
+ <nk-profile-row avatar="MK"><nk-btn variant="secondary" small>Change photo</nk-btn></nk-profile-row>
2075
+ <nk-field label="Display name" desc="Shown next to your comments."><nk-input value="Marcel Karas"></nk-input></nk-field>
2076
+ <nk-field label="Email"><nk-input type="email" value="marcel@monahilft.de"></nk-input></nk-field>
2077
+ </nk-settings-pane>
2078
+ <nk-settings-pane name="appearance" group="Account" icon="🎨" label="Appearance" title="Appearance">
2079
+ <nk-field label="Theme"><nk-select><option>Light</option><option>Dark</option><option>System</option></nk-select></nk-field>
2080
+ </nk-settings-pane>
2081
+ <nk-settings-pane name="members" group="Workspace" icon="👥" label="Members" title="Members">
2082
+ <nk-member-list><nk-member-row name="Sara Lindt" mail="sara@example.com" color="#448361"></nk-member-row></nk-member-list>
2083
+ </nk-settings-pane>
2084
+ </nk-modal>
2085
+ ```
2086
+
2087
+ | Attribute | Type | Default | Description |
2088
+ |---|---|---|---|
2089
+ | `open` | boolean | – | Shown. |
2090
+ | `pane` | string | – | Name of the active pane (default: the pane with `active`, else the first). |
2091
+
2092
+ **Slots:** `(default)` – `nk-settings-pane` children. · `user` – `nk-settings-user` at the top of the nav. · `nav` – Extra nav content below the generated rows (860px rules do not reach slotted elements).
2093
+
2094
+ **Events:** `nk-toggle` `{ open }` – Opened / closed. · `nk-select` `{ value, label }` – Pane switched.
2095
+
2096
+ **Properties:** `open`, `pane`, `panes` · **Methods:** `show(pane?)`, `close()`, `toggle()`
2097
+
2098
+ **Replaces:** `.nk-modal-backdrop`, `.open`, `.nk-modal`, `.nk-settings-nav`, `.nk-settings-content`
2099
+
2100
+ ```html
2101
+ <!-- equivalent class markup -->
2102
+ <div class="nk-modal-backdrop open"><div class="nk-modal">
2103
+ <nav class="nk-settings-nav">
2104
+ <div class="nk-settings-user"><div class="avatar">MK</div><div class="u-text"><div class="name">Marcel Karas</div><div class="mail">marcel@monahilft.de</div></div></div>
2105
+ <div class="nk-section-label">Account</div>
2106
+ <div class="nk-tree-item active"><span class="icon">👤</span><span class="label">My profile</span></div>
2107
+ <div class="nk-tree-item"><span class="icon">🎨</span><span class="label">Appearance</span></div>
2108
+ <div class="nk-section-label">Workspace</div>
2109
+ <div class="nk-tree-item"><span class="icon">👥</span><span class="label">Members</span></div>
2110
+ </nav>
2111
+ <div class="nk-settings-content">
2112
+ <section class="nk-settings-pane active"><h2>My profile</h2>
2113
+ <div class="nk-profile-row"><div class="big-avatar">MK</div><button class="nk-btn secondary small">Change photo</button></div>
2114
+ <div class="nk-field"><div><div class="f-label">Display name</div><div class="f-desc">Shown next to your comments.</div></div><div class="f-control"><input class="nk-input" value="Marcel Karas"></div></div>
2115
+ <div class="nk-field"><div><div class="f-label">Email</div></div><div class="f-control"><input class="nk-input" type="email" value="marcel@monahilft.de"></div></div>
2116
+ </section>
2117
+ </div>
2118
+ </div></div>
2119
+ ```
2120
+
2121
+ **Small screens:** Below 860px the nav collapses to a 60px icon rail; the dialog takes 92vw × 86vh.
2122
+
2123
+ ### 3.65 `<nk-sheet>` – Sheet
2124
+
2125
+ Notion’s mobile surface for menus, properties and more – the phone’s twin of the modal, with its contract: `show()`, `close()`, `toggle()`; Escape and the backdrop close it; focus moves in and back; the page behind is scroll-locked and inert. The panel rises from the bottom edge with a grabber, `title` sits under it and names the dialog. Rows inside are 40px, a thumb’s height. Choosing a row does not close the sheet – `nk-select` bubbles out and the app decides. For a menu that is a popover on the desktop and a sheet on the phone, use `<nk-menu floating sheet>`. Place it directly under `<body>`.
2126
+
2127
+ ```html
2128
+ <nk-sheet open title="More">
2129
+ <nk-tree manual>
2130
+ <nk-section-label>Favourites</nk-section-label>
2131
+ <nk-tree-item icon="🚀">NotionKit MVP</nk-tree-item>
2132
+ <nk-tree-item icon="🎙️">Voice-Office-Hub</nk-tree-item>
2133
+ <nk-section-label>Workspace</nk-section-label>
2134
+ <nk-tree-item icon="🧠">Knowledge base</nk-tree-item>
2135
+ <nk-tree-item icon="🗑️">Trash</nk-tree-item>
2136
+ </nk-tree>
2137
+ </nk-sheet>
2138
+ ```
2139
+
2140
+ | Attribute | Type | Default | Description |
2141
+ |---|---|---|---|
2142
+ | `open` | boolean | – | Shown. |
2143
+ | `title` | string | – | Heading under the grabber and the dialog’s name – never a tooltip. |
2144
+
2145
+ **Slots:** `(default)` – The content: an `nk-tree`, menu items, fields.
2146
+
2147
+ **Events:** `nk-toggle` `{ open }` – Opened / closed.
2148
+
2149
+ **Properties:** `open`, `title` · **Methods:** `show()`, `close()`, `toggle()`
2150
+
2151
+ **Replaces:** `.nk-sheet-backdrop`, `.open`, `.nk-sheet`, `.sh-grabber`, `.sh-title`
2152
+
2153
+ ```html
2154
+ <!-- equivalent class markup -->
2155
+ <div class="nk-sheet-backdrop open"><div class="nk-sheet" role="dialog" aria-modal="true" aria-label="More">
2156
+ <div class="sh-grabber"></div>
2157
+ <div class="sh-title">More</div>
2158
+ <div class="nk-section-label">Favourites</div>
2159
+ <div class="nk-tree-item"><span class="icon">🚀</span><span class="label">NotionKit MVP</span></div>
2160
+ <div class="nk-tree-item"><span class="icon">🎙️</span><span class="label">Voice-Office-Hub</span></div>
2161
+ <div class="nk-section-label">Workspace</div>
2162
+ <div class="nk-tree-item"><span class="icon">🧠</span><span class="label">Knowledge base</span></div>
2163
+ <div class="nk-tree-item"><span class="icon">🗑️</span><span class="label">Trash</span></div>
2164
+ </div></div>
2165
+ ```
2166
+
2167
+ **Small screens:** Made for the phone: full width, above the tab bar, bottom padding from the safe area; the content scrolls inside the sheet. On larger screens at most 640px wide, centred.
2168
+
2169
+ ### 3.66 `<nk-peek>` – Side peek
2170
+
2171
+ Notion’s side peek: a database row opens at the right edge, full height, next to the table, which stays usable – no scrim, nothing inert. The bar carries » to close and your actions (`slot="actions"`); the body is the page – `nk-page-title` (32px here), `nk-props`, a `.nk-prose`, `nk-comments`. `show()` slides it in and moves focus to it; », Escape and a click elsewhere close it, and a click that calls `show()` again – another row – only swaps the content. Clicks inside other overlays leave it open. Place it directly under `<body>`. `resizable` gives it Notion’s drag on the left edge, `inset` moves the page aside instead of covering it.
2172
+
2173
+ ```html
2174
+ <nk-peek open resizable resize-label="Resize" label="Database Table-View">
2175
+ <nk-btn slot="actions" variant="topbar" aria-label="Open as page"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M15 3h6v6M9 21H3v-6M21 3l-7 7M3 21l7-7"/></svg></nk-btn>
2176
+ <nk-page-title>🗃️ Database Table-View</nk-page-title>
2177
+ <nk-props>
2178
+ <nk-prop label="Status" icon="◉"><nk-tag color="blue">In progress</nk-tag></nk-prop>
2179
+ <nk-prop label="Due" icon="📅">20.05.2026</nk-prop>
2180
+ </nk-props>
2181
+ <div class="nk-prose"><p>Table, board and list read the same rows; filters and sort act on all three.</p></div>
2182
+ </nk-peek>
2183
+ ```
2184
+
2185
+ | Attribute | Type | Default | Description |
2186
+ |---|---|---|---|
2187
+ | `open` | boolean | – | Shown. |
2188
+ | `label` | string | – | The dialog’s name – the entry’s title. |
2189
+ | `close-label` | string | `Close` | Name of ». |
2190
+ | `resizable` | boolean | – | The left edge makes it wider or narrower – pointer and arrow keys; the width is the token `--nk-peek-width` on `:root`. |
2191
+ | `width` | px | `560` | Sets the width, within min and max. |
2192
+ | `min` | px | `380` | Narrowest. |
2193
+ | `max` | px | `window − 320` | Widest. |
2194
+ | `resize-label` | string | `Resize` | Name of the edge. |
2195
+ | `inset` | boolean | – | While open on the desktop, the page’s `nk-app` makes room instead of lying under it – for a chart whose bars must stay visible. |
2196
+
2197
+ **Slots:** `(default)` – The page: title, properties, prose, comments. · `actions` – Buttons beside » – open as page, share.
2198
+
2199
+ **Events:** `nk-toggle` `{ open }` – Opened / closed. · `nk-resize` `{ width }` – A drag or a key changed the width – the moment to keep it.
2200
+
2201
+ **Properties:** `open`, `width` · **Methods:** `show()`, `close()`, `toggle()`
2202
+
2203
+ **Replaces:** `.nk-peek-backdrop`, `.open`, `.nk-peek`, `.pk-resize`, `.active`, `.pk-bar`, `.pk-body`
2204
+
2205
+ ```html
2206
+ <!-- equivalent class markup -->
2207
+ <div class="nk-peek-backdrop open"><aside class="nk-peek" role="dialog" aria-label="Database Table-View">
2208
+ <div class="pk-resize" role="separator" aria-orientation="vertical" aria-label="Resize" tabindex="0"></div>
2209
+ <div class="pk-bar"><button class="nk-topbar-btn" aria-label="Close"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="m6 17 5-5-5-5M13 17l5-5-5-5"/></svg></button><button class="nk-topbar-btn" aria-label="Open as page"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M15 3h6v6M9 21H3v-6M21 3l-7 7M3 21l7-7"/></svg></button></div>
2210
+ <div class="pk-body">
2211
+ <h1 class="nk-page-title">🗃️ Database Table-View</h1>
2212
+ <dl class="nk-props">
2213
+ <div class="nk-prop"><dt class="p-name"><span class="p-icon">◉</span>Status</dt><dd class="p-value"><span class="nk-tag blue">In progress</span></dd></div>
2214
+ <div class="nk-prop"><dt class="p-name"><span class="p-icon">📅</span>Due</dt><dd class="p-value">20.05.2026</dd></div>
2215
+ </dl>
2216
+ <div class="nk-prose"><p>Table, board and list read the same rows; filters and sort act on all three.</p></div>
2217
+ </div>
2218
+ </aside></div>
2219
+ ```
2220
+
2221
+ **Small screens:** Below 860px a bottom sheet over a dimmed page, title in 28px – and modal there: the page is inert and scroll-locked, a tap on the dimmed page closes it.
2222
+
2223
+ ### 3.67 `<nk-dialog>` – Dialog
2224
+
2225
+ A question or a short form – “Move to trash?”, the name of a new view, the link between two tasks – with the contract of `nk-modal` and `nk-sheet`: `show()`, `close()`, `toggle()`, `nk-toggle`; Escape – captured, before a peek, a menu or a modal behind it – and the backdrop close it; focus moves in, to an `[autofocus]` element or the first field or button, and back; the page behind is inert and scroll-locked. It lies above the modal and the sheet. `title` is the heading, the default slot the text or the fields, `slot="actions"` the buttons, the confirming one last. A button in `slot="actions"` with a `value` closes it with that value, one anywhere inside with `data-close` too – another button with a value, an option of `nk-segmented` say, leaves it open – and so does the submit of a `<form method="dialog">` inside; before it closes, `nk-close` fires and can be cancelled – the place to check an input. `alert` for a question, `wide` (560px) for a form. `show(from)` hands focus back to `from` – the ⋯ of a menu that closed. Place it directly under `<body>`.
2226
+
2227
+ ```html
2228
+ <nk-dialog open alert title="Move “NotionKit MVP” to trash?">
2229
+ The page and its sub-pages can be restored from Trash for 30 days.
2230
+ <nk-btn slot="actions" variant="secondary" value="">Cancel</nk-btn>
2231
+ <nk-btn slot="actions" variant="danger-solid" value="trash">Move to trash</nk-btn>
2232
+ </nk-dialog>
2233
+ ```
2234
+
2235
+ | Attribute | Type | Default | Description |
2236
+ |---|---|---|---|
2237
+ | `open` | boolean | – | Shown. |
2238
+ | `title` | string | – | Heading and the dialog’s name – never a tooltip. |
2239
+ | `alert` | boolean | – | An alertdialog, described by its text. |
2240
+ | `wide` | boolean | – | 560px instead of 440px. |
2241
+
2242
+ **Slots:** `(default)` – The text or the fields. · `actions` – The buttons, right; a `value` closes with it.
2243
+
2244
+ **Events:** `nk-close` `{ value }` – About to close – cancel it to keep it open. · `nk-toggle` `{ open }` – Opened / closed.
2245
+
2246
+ **Properties:** `open`, `returnValue`, `title` · **Methods:** `show(from)`, `close(value)`, `toggle()`
2247
+
2248
+ **Replaces:** `.nk-dialog-backdrop`, `.open`, `.nk-dialog`, `.wide`, `.dl-title`, `.dl-body`, `.dl-actions`
2249
+
2250
+ ```html
2251
+ <!-- equivalent class markup -->
2252
+ <div class="nk-dialog-backdrop open"><div class="nk-dialog" role="alertdialog" aria-modal="true" aria-label="Move to trash">
2253
+ <div class="dl-title">Move “NotionKit MVP” to trash?</div>
2254
+ <div class="dl-body">The page and its sub-pages can be restored from Trash for 30 days.</div>
2255
+ <div class="dl-actions"><button class="nk-btn secondary">Cancel</button><button class="nk-btn danger-solid">Move to trash</button></div>
2256
+ </div></div>
2257
+ ```
2258
+
2259
+ **Small screens:** Below 860px a bottom sheet with a grabber, the buttons stacked across the width, the confirming one on top.
2260
+
2261
+ ### 3.68 `<nk-settings-pane>` – Settings pane
2262
+
2263
+ One pane of the settings modal. `label`, `icon` and `group` feed the modal’s nav; `title` renders the pane heading. Slotted `<h2>`/`<h3>` are styled too.
2264
+
2265
+ ```html
2266
+ <nk-settings-pane title="Notifications" active><h3>Email</h3><nk-field label="Email notifications"><nk-switch checked></nk-switch></nk-field></nk-settings-pane>
2267
+ ```
2268
+
2269
+ | Attribute | Type | Default | Description |
2270
+ |---|---|---|---|
2271
+ | `name` | string | – | Identifier used by `pane`. |
2272
+ | `label` | string | – | Nav label (a pane without label gets no nav row). |
2273
+ | `icon` | string | – | Nav icon. |
2274
+ | `group` | string | – | Section label above its nav rows. |
2275
+ | `title` | string | – | Pane heading. Read once and taken off the host, so it never shows as a tooltip. |
2276
+ | `active` | boolean | – | Visible (managed by the modal). |
2277
+
2278
+ **Slots:** `(default)` – Fields, headings, anything.
2279
+
2280
+ **Replaces:** `.nk-settings-pane`, `.active`
2281
+
2282
+ ```html
2283
+ <!-- equivalent class markup -->
2284
+ <section class="nk-settings-pane active"><h2>Notifications</h2><h3>Email</h3><div class="nk-field"><div><div class="f-label">Email notifications</div></div><div class="f-control"><button class="nk-switch" role="switch" aria-checked="true"></button></div></div></section>
2285
+ ```
2286
+
2287
+ **Small screens:** Content padding drops to 24px below 860px.
2288
+
2289
+ ### 3.69 `<nk-settings-user>` – Settings user
2290
+
2291
+ The user card at the top of the settings nav.
2292
+
2293
+ ```html
2294
+ <div style="background:var(--nk-bg-sidebar);border-radius:8px;max-width:230px;padding:10px 8px"><nk-settings-user name="Marcel Karas" mail="marcel@monahilft.de"></nk-settings-user></div>
2295
+ ```
2296
+
2297
+ | Attribute | Type | Default | Description |
2298
+ |---|---|---|---|
2299
+ | `name` | string | – | Name. |
2300
+ | `mail` | string | – | Mail. |
2301
+ | `avatar` | string | – | Initials (default: from the name). |
2302
+
2303
+ **Slots:** `avatar` – Custom avatar.
2304
+
2305
+ **Replaces:** `.nk-settings-user`, `.avatar`, `.u-text`, `.name`, `.mail`
2306
+
2307
+ ```html
2308
+ <!-- equivalent class markup -->
2309
+ <div style="background:var(--nk-bg-sidebar);border-radius:8px;max-width:230px;padding:10px 8px"><div class="nk-settings-user"><div class="avatar">MK</div><div class="u-text"><div class="name">Marcel Karas</div><div class="mail">marcel@monahilft.de</div></div></div></div>
2310
+ ```
2311
+
2312
+ **Small screens:** Below 860px only the avatar remains.
2313
+
2314
+ ### 3.70 `<nk-cmdk>` – Command palette
2315
+
2316
+ ⌘K. Feed it `palette.commands = [{ group, items: [{ id, icon, label, shortcut, keywords, action }] }]`; it searches fuzzily over label and keywords, keeps group order, moves the selection with ↑↓, picks with Enter or click (`nk-command` plus the item’s `action`), and closes on Escape or the backdrop. The hotkey is `mod+k` unless changed. Place it directly under `<body>`.
2317
+
2318
+ ```html
2319
+ <nk-cmdk open placeholder="Search or type a command …"></nk-cmdk>
2320
+ <script>
2321
+ document.querySelector('nk-cmdk').commands = [
2322
+ { group: 'Pages', items: [
2323
+ { id: 'mvp', icon: '🚀', label: 'NotionKit MVP' },
2324
+ { id: 'voh', icon: '🎙️', label: 'Voice-Office-Hub' },
2325
+ { id: 'kb', icon: '🧠', label: 'Knowledge base' },
2326
+ ]},
2327
+ { group: 'Actions', items: [
2328
+ { id: 'new', icon: '+', label: 'Create new page', shortcut: '⌘N' },
2329
+ { id: 'theme', icon: '🌙', label: 'Toggle theme', shortcut: '⌘⇧L' },
2330
+ { id: 'settings', icon: '⚙️', label: 'Open settings', shortcut: '⌘,' },
2331
+ ]},
2332
+ ];
2333
+ </script>
2334
+ ```
2335
+
2336
+ | Attribute | Type | Default | Description |
2337
+ |---|---|---|---|
2338
+ | `open` | boolean | – | Shown. |
2339
+ | `hotkey` | string | `mod+k` | Global shortcut, e.g. `mod+k`, `mod+shift+p`. |
2340
+ | `placeholder` | string | – | Input placeholder. |
2341
+
2342
+ **Slots:** `footer` – Replaces the default key hints.
2343
+
2344
+ **Events:** `nk-command` `{ id, item, query }` – An item was picked; `preventDefault()` skips `item.action`. · `nk-toggle` `{ open }` – Opened / closed.
2345
+
2346
+ **Properties:** `commands`, `open`, `query` · **Methods:** `show()`, `close()`, `toggle()`, `results()`, `pick(index?)`
2347
+
2348
+ **Replaces:** `.nk-cmdk-backdrop`, `.open`, `.nk-cmdk`, `.nk-cmdk-input-row`, `.nk-cmdk-list`, `.nk-cmdk-group`, `.nk-cmdk-item`, `.selected`, `.m-icon`, `.m-shortcut`, `.nk-cmdk-empty`, `.nk-cmdk-footer`
2349
+
2350
+ ```html
2351
+ <!-- equivalent class markup -->
2352
+ <div class="nk-cmdk-backdrop open"><div class="nk-cmdk">
2353
+ <div class="nk-cmdk-input-row"><span style="font-size:15px">🔍</span><input placeholder="Search or type a command …"><kbd class="nk-kbd">esc</kbd></div>
2354
+ <div class="nk-cmdk-list">
2355
+ <div class="nk-cmdk-group">Pages</div>
2356
+ <div class="nk-cmdk-item selected"><span class="m-icon">🚀</span><span>NotionKit MVP</span></div>
2357
+ <div class="nk-cmdk-item"><span class="m-icon">🎙️</span><span>Voice-Office-Hub</span></div>
2358
+ <div class="nk-cmdk-item"><span class="m-icon">🧠</span><span>Knowledge base</span></div>
2359
+ <div class="nk-cmdk-group">Actions</div>
2360
+ <div class="nk-cmdk-item"><span class="m-icon">+</span><span>Create new page</span><span class="m-shortcut">⌘N</span></div>
2361
+ <div class="nk-cmdk-item"><span class="m-icon">🌙</span><span>Toggle theme</span><span class="m-shortcut">⌘⇧L</span></div>
2362
+ <div class="nk-cmdk-item"><span class="m-icon">⚙️</span><span>Open settings</span><span class="m-shortcut">⌘,</span></div>
2363
+ </div>
2364
+ <div class="nk-cmdk-footer"><span><kbd class="nk-kbd">↑</kbd><kbd class="nk-kbd">↓</kbd> navigate</span><span><kbd class="nk-kbd">↵</kbd> open</span><span><kbd class="nk-kbd">⌘</kbd><kbd class="nk-kbd">K</kbd> toggle</span></div>
2365
+ </div></div>
2366
+ ```
2367
+
2368
+ **Small screens:** Full width (96vw) and closer to the top below 860px.
2369
+
2370
+ ### 3.71 `<nk-menu>` – Menu
2371
+
2372
+ A 230px context menu. Items are `nk-menu-item`s (`type="separator"` / `"label"` for the rest); ↑↓ move, Enter selects, `nk-select` bubbles up. Inside `nk-pop` or the workspace switcher it is part of their surface. With `floating` it is a menu over the page of its own, NotionKit’s `.nk-pop.floating`: `menu.show(button)` opens it under the button, right edges aligned (`align="start"`: left edges), and it fades in like the palette. A tap outside closes it and reaches nothing else; Escape and a chosen item close it, a switch row keeps it open. Opened from the keyboard, focus moves to the first item and back when it closes. Put it directly under `<body>`, like the other overlays. Floating, it opens upwards where the window ends below and there is more room above, and a menu longer than the room scrolls inside; in an `nk-modal` or `nk-dialog` it sits right as well.
2373
+
2374
+ ```html
2375
+ <nk-menu>
2376
+ <nk-menu-item type="label">Page</nk-menu-item>
2377
+ <nk-menu-item icon="✏️" shortcut="⌘E" value="rename">Rename</nk-menu-item>
2378
+ <nk-menu-item icon="📄" shortcut="⌘D" value="duplicate">Duplicate</nk-menu-item>
2379
+ <nk-menu-item icon="📁" value="move">Move to …</nk-menu-item>
2380
+ <nk-menu-item type="separator"></nk-menu-item>
2381
+ <nk-menu-item icon="🗑️" danger value="delete">Delete</nk-menu-item>
2382
+ </nk-menu>
2383
+ ```
2384
+
2385
+ | Attribute | Type | Default | Description |
2386
+ |---|---|---|---|
2387
+ | `floating` | boolean | – | A menu over the page: fixed, closed until `open`, fades in. |
2388
+ | `sheet` | boolean | – | Below 860px a bottom sheet (with `floating`). |
2389
+ | `open` | boolean | – | Shown (with `floating`). |
2390
+ | `align` | end | start | `end` | Which edges `show(anchor)` aligns. |
2391
+
2392
+ **Slots:** `(default)` – `nk-menu-item` children.
2393
+
2394
+ **Events:** `nk-select` `{ value, label, item }` – From the chosen item. · `nk-toggle` `{ open }` – A floating menu opened / closed.
2395
+
2396
+ **Properties:** `open`, `items` · **Methods:** `show(anchor?)`, `close()`, `toggle(anchor?)`, `focusFirst()`
2397
+
2398
+ **Replaces:** `.nk-pop`, `.nk-menu`, `.nk-menu-item`, `.m-icon`, `.m-shortcut`, `.danger`, `.nk-menu-sep`, `.nk-menu-label`, `.floating`, `.sheet`, `.open`
2399
+
2400
+ ```html
2401
+ <!-- equivalent class markup -->
2402
+ <div class="nk-pop nk-menu">
2403
+ <div class="nk-menu-label">Page</div>
2404
+ <div class="nk-menu-item"><span class="m-icon">✏️</span>Rename<span class="m-shortcut">⌘E</span></div>
2405
+ <div class="nk-menu-item"><span class="m-icon">📄</span>Duplicate<span class="m-shortcut">⌘D</span></div>
2406
+ <div class="nk-menu-item"><span class="m-icon">📁</span>Move to …</div>
2407
+ <div class="nk-menu-sep"></div>
2408
+ <div class="nk-menu-item danger"><span class="m-icon">🗑️</span>Delete</div>
2409
+ </div>
2410
+ ```
2411
+
2412
+ **Small screens:** With `sheet` the floating menu is a bottom sheet below 860px – full width, a grabber, 40px rows, the page dimmed – whatever position `show()` wrote: one markup, two presentations, as Notion’s mobile app opens every menu.
2413
+
2414
+ ### 3.72 `<nk-menu-item>` – Menu item
2415
+
2416
+ One row of `nk-menu`: icon, label, shortcut; `danger` for destructive actions. `type` switches to a separator or a group label, or to a row with a switch on the right – “Small text” in Notion’s page menu: a click flips `checked` and fires `nk-change`, so the menu stays open. `type="check"` puts a ✓ where the shortcut stands, as in a filter menu – for a choice of several: a click flips `checked` and fires `nk-change { value, checked }` instead of `nk-select`, and the menu stays open.
2417
+
2418
+ ```html
2419
+ <nk-menu><nk-menu-item icon="✏️" shortcut="⌘E" value="rename">Rename</nk-menu-item><nk-menu-item type="switch" icon="🔡" value="small" checked>Small text</nk-menu-item></nk-menu>
2420
+ ```
2421
+
2422
+ | Attribute | Type | Default | Description |
2423
+ |---|---|---|---|
2424
+ | `icon` | string | – | Leading icon. |
2425
+ | `shortcut` | string | – | Trailing shortcut text. |
2426
+ | `value` | string | – | Reported value (default: text). |
2427
+ | `danger` | boolean | – | Red text. |
2428
+ | `type` | item | separator | label | switch | check | `item` | Row kind. |
2429
+ | `checked` | boolean | – | The switch is on, the check shows (type="switch" / "check"). |
2430
+ | `disabled` | boolean | – | Not selectable. |
2431
+
2432
+ **Slots:** `(default)` – Label. · `icon` – Icon node.
2433
+
2434
+ **Events:** `nk-select` `{ value, label, item }` – Clicked / Enter. · `nk-change` `{ value, checked, item }` – A switch or check row flipped.
2435
+
2436
+ **Replaces:** `.nk-menu-item`, `.danger`, `.nk-menu-sep`, `.nk-menu-label`
2437
+
2438
+ ```html
2439
+ <!-- equivalent class markup -->
2440
+ <div class="nk-pop nk-menu"><div class="nk-menu-item"><span class="m-icon">✏️</span>Rename<span class="m-shortcut">⌘E</span></div><div class="nk-menu-item" role="menuitemcheckbox" aria-checked="true"><span class="m-icon">🔡</span>Small text<span class="nk-switch" aria-hidden="true" aria-checked="true"></span></div></div>
2441
+ ```
2442
+
2443
+ **Small screens:** Unchanged.
2444
+
2445
+ ### 3.73 `<nk-pop>` – Popover
2446
+
2447
+ Anchors a floating surface to a trigger. The trigger goes in `slot="trigger"` and toggles `open`; outside clicks, Escape and an `nk-select` from inside close it. Content is wrapped in `.nk-pop` unless it brings its own surface (`nk-menu`, `nk-emoji-picker`) or `bare` is set.
2448
+
2449
+ ```html
2450
+ <div style="min-height:220px"><nk-pop open>
2451
+ <nk-btn slot="trigger" variant="secondary">Options ▾</nk-btn>
2452
+ <nk-menu><nk-menu-item icon="✏️" value="rename">Rename</nk-menu-item><nk-menu-item icon="📄" value="duplicate">Duplicate</nk-menu-item><nk-menu-item type="separator"></nk-menu-item><nk-menu-item icon="🗑️" danger value="delete">Delete</nk-menu-item></nk-menu>
2453
+ </nk-pop></div>
2454
+ ```
2455
+
2456
+ | Attribute | Type | Default | Description |
2457
+ |---|---|---|---|
2458
+ | `open` | boolean | – | Shown. |
2459
+ | `placement` | bottom-start | bottom-end | top-start | top-end | `bottom-start` | Where the surface opens. |
2460
+ | `bare` | boolean | – | No `.nk-pop` wrapper. |
2461
+
2462
+ **Slots:** `trigger` – The button. · `(default)` – The floating content.
2463
+
2464
+ **Events:** `nk-toggle` `{ open }` – Opened / closed.
2465
+
2466
+ **Methods:** `show()`, `close()`, `toggle()`
2467
+
2468
+ **Replaces:** `.nk-pop`
2469
+
2470
+ ```html
2471
+ <!-- equivalent class markup -->
2472
+ <div style="min-height:220px"><div style="position:relative;display:inline-block">
2473
+ <button class="nk-btn secondary">Options ▾</button>
2474
+ <div style="position:absolute;top:100%;left:0;margin-top:4px;z-index:50"><div class="nk-pop nk-menu"><div class="nk-menu-item"><span class="m-icon">✏️</span>Rename</div><div class="nk-menu-item"><span class="m-icon">📄</span>Duplicate</div><div class="nk-menu-sep"></div><div class="nk-menu-item danger"><span class="m-icon">🗑️</span>Delete</div></div></div>
2475
+ </div></div>
2476
+ ```
2477
+
2478
+ **Small screens:** Positioned relative to the trigger; keep it near the viewport edge in mind.
2479
+
2480
+ ### 3.74 `<nk-emoji-picker>` – Emoji picker
2481
+
2482
+ Search field, 8-column grid, category strip. Ships with a built-in set (names for search); `picker.emojis = [{ char, name, cat }]` replaces it. A click fires `nk-select { emoji }`.
2483
+
2484
+ ```html
2485
+ <nk-emoji-picker placeholder="Search…"></nk-emoji-picker>
2486
+ ```
2487
+
2488
+ | Attribute | Type | Default | Description |
2489
+ |---|---|---|---|
2490
+ | `placeholder` | string | – | Search placeholder. |
2491
+ | `value` | string | – | Last picked emoji. |
2492
+
2493
+ **Events:** `nk-select` `{ emoji, value }` – Emoji picked.
2494
+
2495
+ **Properties:** `emojis`, `value`
2496
+
2497
+ **Replaces:** `.nk-pop`, `.nk-emoji-search`, `.nk-emoji-grid`, `.nk-emoji-cats`, `.active`
2498
+
2499
+ ```html
2500
+ <!-- equivalent class markup -->
2501
+ <div class="nk-pop"><input class="nk-emoji-search" placeholder="Search…"><div class="nk-emoji-grid"><span>😀</span><span>😊</span><span>😂</span><span>🙂</span><span>😉</span><span>😍</span><span>🤔</span><span>😎</span><span>🥳</span><span>😴</span><span>🤯</span><span>😅</span><span>🙃</span><span>😇</span><span>🤗</span><span>😢</span></div><div class="nk-emoji-cats"><span class="active">😀</span><span>👋</span><span>🌿</span><span>☕</span><span>🎯</span><span>🚀</span><span>💡</span><span>✅</span></div></div>
2502
+ ```
2503
+
2504
+ **Small screens:** 296px wide; fine on any phone.
2505
+
2506
+ ### 3.75 `<nk-toast>` – Toast
2507
+
2508
+ One inverted pill at the bottom centre, above every overlay; over a tab bar at the bottom of the screen it rises 12px above the bar. `toast.show("Saved")` shows it and hides it after `duration` ms; `open` is the state.
2509
+
2510
+ ```html
2511
+ <nk-toast open duration="0">Settings saved</nk-toast>
2512
+ ```
2513
+
2514
+ | Attribute | Type | Default | Description |
2515
+ |---|---|---|---|
2516
+ | `open` | boolean | – | Visible. |
2517
+ | `duration` | ms | `2200` | Auto-hide delay (0 = stay). |
2518
+ | `icon` | string | `✓` | Leading glyph. |
2519
+
2520
+ **Slots:** `(default)` – Static content (when `show()` gets no message).
2521
+
2522
+ **Events:** `nk-toggle` `{ open }` – Shown / hidden.
2523
+
2524
+ **Properties:** `open`, `message` · **Methods:** `show(message?, { duration })`, `close()`
2525
+
2526
+ **Replaces:** `.nk-toast`, `.show`
2527
+
2528
+ ```html
2529
+ <!-- equivalent class markup -->
2530
+ <div class="nk-toast show">✓ <span>Settings saved</span></div>
2531
+ ```
2532
+
2533
+ **Small screens:** Unchanged.
2534
+
2535
+ ### 3.76 `<nk-tooltip>` – Tooltip
2536
+
2537
+ Notion’s hover hint in the toast’s colours, a shortcut muted beside it. One `<nk-tooltip>` without `for` serves every element with `data-tooltip` (and `data-tooltip-key`) on the page, inside shadow roots too – a whole toolbar in one element; with `for` it belongs to one element and shows its own content and `shortcut`. It appears after `delay` ms (400) under the pointer and at once on keyboard focus, never on touch; leaving, a press, blur, scrolling and Escape hide it. It sits 6px below its target and centred, above it where the window ends, 8px inside the window – `placeNear` in `util/floating.js`. `show(target, text, shortcut)` takes an element or a rect, for what has no element of its own: the bars of a chart. The target’s `aria-describedby` names it while it shows. Hover the buttons. A tooltip shown with `show(rect, …)` stays until `hide()`, the pointer on another `[data-tooltip]`, a press or Escape; the wheel hides it too, also inside a shadow root. A line break in the text is kept.
2538
+
2539
+ ```html
2540
+ <div style="display:flex;gap:16px;align-items:center;padding:4px 0 44px">
2541
+ <nk-btn variant="topbar" aria-label="Style, export and more" data-tooltip="Style, export and more">⋯</nk-btn>
2542
+ <nk-btn variant="topbar" aria-label="Add to Favourites" data-tooltip="Add to Favourites">⭐</nk-btn>
2543
+ <nk-btn variant="topbar" id="ttSidebar" aria-label="Open sidebar">☰</nk-btn>
2544
+ </div>
2545
+ <nk-tooltip></nk-tooltip>
2546
+ <nk-tooltip for="ttSidebar" shortcut="⌘\">Open sidebar</nk-tooltip>
2547
+ ```
2548
+
2549
+ | Attribute | Type | Default | Description |
2550
+ |---|---|---|---|
2551
+ | `for` | id | – | The one element it belongs to; without it, every `[data-tooltip]`. |
2552
+ | `shortcut` | string | – | With `for`: the shortcut beside the text. |
2553
+ | `delay` | ms | `400` | Wait under the pointer. |
2554
+ | `placement` | bottom | top | `bottom` | Preferred side. |
2555
+
2556
+ **Slots:** `(default)` – With `for`: the text.
2557
+
2558
+ **Properties:** `open` · **Methods:** `show(target, text, shortcut)`, `hide()`
2559
+
2560
+ **Replaces:** `.nk-tooltip`, `.open`, `.tt-key`
2561
+
2562
+ **Small screens:** No hover on a phone: it shows nothing on touch.
2563
+
2564
+ ## Data & collaboration (wave 5)
2565
+
2566
+ ### 3.77 `<nk-database>` – Database
2567
+
2568
+ The view switcher with Notion’s toolbar: child views (`nk-table-view`, `nk-board-view`, `nk-list-view`) become the tabs on the left, `slot="tools"` holds the view’s tools on the right – `<nk-btn variant="tool">` for Filter, Sort and search, then “New” – and `slot="filters"` the `nk-filter-bar` under them. `columns` and `rows` are pushed into every view. `view` selects the active one; `count` on a view shows the row count as badge. No fetching: give it data, listen to events.
2569
+
2570
+ ```html
2571
+ <nk-database view="table" add-view>
2572
+ <nk-btn slot="tools" variant="tool" active>Filter</nk-btn>
2573
+ <nk-btn slot="tools" variant="tool">Sort</nk-btn>
2574
+ <nk-btn slot="tools" variant="primary" small>New</nk-btn>
2575
+ <nk-table-view name="table" label="▦ Table" count new-row sortable></nk-table-view>
2576
+ <nk-board-view name="board" label="▤ Board" group-by="status" new-row></nk-board-view>
2577
+ </nk-database>
2578
+ <script>{
2579
+ const db = document.currentScript.previousElementSibling;
2580
+ db.columns = [
2581
+ { key: 'name', label: 'Name', type: 'text', icon: '📄', title: true },
2582
+ { key: 'status', label: 'Status', type: 'select', icon: '◉', options: [
2583
+ { value: 'planned', label: 'Planned', color: 'orange' }, { value: 'progress', label: 'In progress', color: 'blue' }, { value: 'done', label: 'Done', color: 'green' } ] },
2584
+ { key: 'owner', label: 'Owner', type: 'person', icon: '👤' },
2585
+ { key: 'due', label: 'Due', type: 'date', icon: '📅' },
2586
+ { key: 'progress', label: 'Progress', type: 'progress', icon: '▰' },
2587
+ { key: 'effort', label: 'Effort (h)', type: 'number', icon: '#', locale: 'en', format: { minimumFractionDigits: 1 } },
2588
+ ];
2589
+ db.rows = [
2590
+ { id: 1, icon: '🧭', name: 'App shell & sidebar', status: 'done', owner: { name: 'Marcel', initials: 'MK', color: 'purple' }, due: '08.05.2026', progress: 100, effort: 6 },
2591
+ { id: 2, icon: '📄', name: 'Page shell & typography', status: 'done', owner: { name: 'Marcel', initials: 'MK', color: 'purple' }, due: '10.05.2026', progress: 100, effort: 4.5 },
2592
+ { id: 3, icon: '🗃️', name: 'Database table view', status: 'progress', owner: { name: 'Marcel', initials: 'MK', color: 'purple' }, due: '20.05.2026', progress: 65, effort: 12.5 },
2593
+ { id: 4, icon: '▤', name: 'Board view & drag-and-drop', status: 'planned', due: '02.06.2026', progress: 0, effort: 8 },
2594
+ ];
2595
+ }</script>
2596
+ ```
2597
+
2598
+ | Attribute | Type | Default | Description |
2599
+ |---|---|---|---|
2600
+ | `view` | string | – | Name of the active view. |
2601
+ | `add-view` | boolean | – | Show a + tab (fires `nk-action`). |
2602
+
2603
+ **Slots:** `(default)` – View elements. · `tools` – The tools right of the tabs: `<nk-btn variant="tool">`, a small primary “New”. · `filters` – Under the toolbar: `nk-filter-bar`.
2604
+
2605
+ **Events:** `nk-view-change` `{ view }` – Tab switched. · `nk-action` `{ action: 'add-view' }` – + clicked. · `nk-select / nk-change / nk-action` `(from the views)` – Bubble up from the active view.
2606
+
2607
+ **Properties:** `columns`, `rows`, `view`, `views` · **Methods:** `refresh()`
2608
+
2609
+ **Replaces:** `.nk-database`, `.nk-db-toolbar`, `.nk-db-tabs`, `.nk-db-tab`, `.active`, `.badge`, `.add`, `.tools`
2610
+
2611
+ ```html
2612
+ <!-- equivalent class markup -->
2613
+ <div class="nk-database">
2614
+ <div class="nk-db-toolbar">
2615
+ <div class="nk-db-tabs"><span class="nk-db-tab active">▦ Table <span class="badge">4</span></span><span class="nk-db-tab">▤ Board</span><span class="nk-db-tab add">+</span></div>
2616
+ <div class="tools"><button class="nk-db-tool active">Filter</button><button class="nk-db-tool">Sort</button><button class="nk-btn primary small">New</button></div>
2617
+ </div>
2618
+ <div class="nk-table-wrap"><table class="nk-table">
2619
+ <thead><tr><th><span class="th-icon">📄</span>Name</th><th><span class="th-icon">◉</span>Status</th><th><span class="th-icon">👤</span>Owner</th><th><span class="th-icon">📅</span>Due</th><th><span class="th-icon">▰</span>Progress</th><th><span class="th-icon">#</span>Effort (h)</th></tr></thead>
2620
+ <tbody>
2621
+ <tr><td><span class="row-title">🧭 App shell & sidebar</span></td><td><span class="nk-tag green">Done</span></td><td><span class="person-cell"><span class="nk-avatar small purple">MK</span> Marcel</span></td><td><span class="date-cell">08.05.2026</span></td><td><span class="nk-progress"><i style="width:100%"></i></span><span class="nk-progress-label">100%</span></td><td class="num"><span>6.0</span></td></tr>
2622
+ <tr><td><span class="row-title">📄 Page shell & typography</span></td><td><span class="nk-tag green">Done</span></td><td><span class="person-cell"><span class="nk-avatar small purple">MK</span> Marcel</span></td><td><span class="date-cell">10.05.2026</span></td><td><span class="nk-progress"><i style="width:100%"></i></span><span class="nk-progress-label">100%</span></td><td class="num"><span>4.5</span></td></tr>
2623
+ <tr><td><span class="row-title">🗃️ Database table view</span></td><td><span class="nk-tag blue">In progress</span></td><td><span class="person-cell"><span class="nk-avatar small purple">MK</span> Marcel</span></td><td><span class="date-cell">20.05.2026</span></td><td><span class="nk-progress"><i style="width:65%"></i></span><span class="nk-progress-label">65%</span></td><td class="num"><span>12.5</span></td></tr>
2624
+ <tr><td><span class="row-title">▤ Board view & drag-and-drop</span></td><td><span class="nk-tag orange">Planned</span></td><td><span class="person-cell">—</span></td><td><span class="date-cell">02.06.2026</span></td><td><span class="nk-progress"><i style="width:0%"></i></span><span class="nk-progress-label">0%</span></td><td class="num"><span>8.0</span></td></tr>
2625
+ </tbody>
2626
+ </table><div class="nk-new-row">+ New page</div></div>
2627
+ </div>
2628
+ ```
2629
+
2630
+ **Small screens:** The tabs scroll sideways when the row gets narrow; the tools keep their place. Tables and boards scroll horizontally; nothing breaks.
2631
+
2632
+ ### 3.78 `<nk-table-view>` – Table view
2633
+
2634
+ Renders `columns` × `rows` as the NotionKit table. Cells are polymorphic (`text`, `select`, `multi-select`, `date`, `person`, `checkbox`, `url`, `number`, `progress`) and rendered as plain markup by the exported `renderPropertyCell()` – every cell rule starts with `.nk-table`, so a cell element of its own would never be styled. Header clicks sort with `sortable`. A `number` column stands right-aligned in figures of equal width, formatted by its `locale` and `format` (Intl.NumberFormat options); a person’s `color` takes one of the nine names.
2635
+
2636
+ ```html
2637
+ <nk-table-view new-row sortable></nk-table-view>
2638
+ <script>{
2639
+ const db = document.currentScript.previousElementSibling;
2640
+ db.columns = [
2641
+ { key: 'name', label: 'Name', type: 'text', icon: '📄', title: true },
2642
+ { key: 'status', label: 'Status', type: 'select', icon: '◉', options: [
2643
+ { value: 'planned', label: 'Planned', color: 'orange' }, { value: 'progress', label: 'In progress', color: 'blue' }, { value: 'done', label: 'Done', color: 'green' } ] },
2644
+ { key: 'owner', label: 'Owner', type: 'person', icon: '👤' },
2645
+ { key: 'due', label: 'Due', type: 'date', icon: '📅' },
2646
+ { key: 'progress', label: 'Progress', type: 'progress', icon: '▰' },
2647
+ { key: 'effort', label: 'Effort (h)', type: 'number', icon: '#', locale: 'en', format: { minimumFractionDigits: 1 } },
2648
+ ];
2649
+ db.rows = [
2650
+ { id: 1, icon: '🧭', name: 'App shell & sidebar', status: 'done', owner: { name: 'Marcel', initials: 'MK', color: 'purple' }, due: '08.05.2026', progress: 100, effort: 6 },
2651
+ { id: 2, icon: '📄', name: 'Page shell & typography', status: 'done', owner: { name: 'Marcel', initials: 'MK', color: 'purple' }, due: '10.05.2026', progress: 100, effort: 4.5 },
2652
+ { id: 3, icon: '🗃️', name: 'Database table view', status: 'progress', owner: { name: 'Marcel', initials: 'MK', color: 'purple' }, due: '20.05.2026', progress: 65, effort: 12.5 },
2653
+ { id: 4, icon: '▤', name: 'Board view & drag-and-drop', status: 'planned', due: '02.06.2026', progress: 0, effort: 8 },
2654
+ ];
2655
+ }</script>
2656
+ ```
2657
+
2658
+ | Attribute | Type | Default | Description |
2659
+ |---|---|---|---|
2660
+ | `name` | string | – | View name (tab id). |
2661
+ | `label` | string | – | Tab label. |
2662
+ | `badge` | string | – | Tab badge. |
2663
+ | `count` | boolean | – | Row count as badge. |
2664
+ | `new-row` | boolean | – | Show the add row. |
2665
+ | `new-row-label` | string | `+ New page` | Its text. |
2666
+ | `sortable` | boolean | – | Header click sorts locally. |
2667
+ | `sort-key` | string | – | Sorted column. |
2668
+ | `sort-dir` | asc | desc | – | Direction. |
2669
+ | `wrap` | boolean | – | Cell text may break (Notion's "wrap column"). |
2670
+
2671
+ **Events:** `nk-select` `{ row, id }` – Row clicked. · `nk-change` `{ row, key, value }` – Checkbox cell toggled (row updated in place). · `nk-action` `{ action: 'sort' | 'new-row', key?, value? }` – Header or add row clicked.
2672
+
2673
+ **Properties:** `columns`, `rows`, `data` · **Methods:** `refresh()`
2674
+
2675
+ **Replaces:** `.nk-table-wrap`, `.nk-table`, `.wrap`, `.th-icon`, `.row-title`, `.date-cell`, `.person-cell`, `.nk-avatar`, `.num`, `.nk-new-row`
2676
+
2677
+ ```html
2678
+ <!-- equivalent class markup -->
2679
+ <div class="nk-table-wrap"><table class="nk-table">
2680
+ <thead><tr><th><span class="th-icon">📄</span>Name</th><th><span class="th-icon">◉</span>Status</th><th><span class="th-icon">👤</span>Owner</th><th><span class="th-icon">📅</span>Due</th><th><span class="th-icon">▰</span>Progress</th><th><span class="th-icon">#</span>Effort (h)</th></tr></thead>
2681
+ <tbody>
2682
+ <tr><td><span class="row-title">🧭 App shell & sidebar</span></td><td><span class="nk-tag green">Done</span></td><td><span class="person-cell"><span class="nk-avatar small purple">MK</span> Marcel</span></td><td><span class="date-cell">08.05.2026</span></td><td><span class="nk-progress"><i style="width:100%"></i></span><span class="nk-progress-label">100%</span></td><td class="num"><span>6.0</span></td></tr>
2683
+ <tr><td><span class="row-title">📄 Page shell & typography</span></td><td><span class="nk-tag green">Done</span></td><td><span class="person-cell"><span class="nk-avatar small purple">MK</span> Marcel</span></td><td><span class="date-cell">10.05.2026</span></td><td><span class="nk-progress"><i style="width:100%"></i></span><span class="nk-progress-label">100%</span></td><td class="num"><span>4.5</span></td></tr>
2684
+ <tr><td><span class="row-title">🗃️ Database table view</span></td><td><span class="nk-tag blue">In progress</span></td><td><span class="person-cell"><span class="nk-avatar small purple">MK</span> Marcel</span></td><td><span class="date-cell">20.05.2026</span></td><td><span class="nk-progress"><i style="width:65%"></i></span><span class="nk-progress-label">65%</span></td><td class="num"><span>12.5</span></td></tr>
2685
+ <tr><td><span class="row-title">▤ Board view & drag-and-drop</span></td><td><span class="nk-tag orange">Planned</span></td><td><span class="person-cell">—</span></td><td><span class="date-cell">02.06.2026</span></td><td><span class="nk-progress"><i style="width:0%"></i></span><span class="nk-progress-label">0%</span></td><td class="num"><span>8.0</span></td></tr>
2686
+ </tbody>
2687
+ </table><div class="nk-new-row">+ New page</div></div>
2688
+ ```
2689
+
2690
+ **Small screens:** Scrolls horizontally inside `.nk-table-wrap`.
2691
+
2692
+ ### 3.79 `<nk-board-view>` – Board view
2693
+
2694
+ Groups rows by a select column (`group-by`, default: the first select column) into one column per option. Cards show the title column and the `meta-keys` (default: dates and progress). Drag a card onto another column: the row’s value changes and `nk-change` fires.
2695
+
2696
+ ```html
2697
+ <nk-board-view group-by="status" new-row></nk-board-view>
2698
+ <script>{
2699
+ const db = document.currentScript.previousElementSibling;
2700
+ db.columns = [
2701
+ { key: 'name', label: 'Name', type: 'text', icon: '📄', title: true },
2702
+ { key: 'status', label: 'Status', type: 'select', icon: '◉', options: [
2703
+ { value: 'planned', label: 'Planned', color: 'orange' }, { value: 'progress', label: 'In progress', color: 'blue' }, { value: 'done', label: 'Done', color: 'green' } ] },
2704
+ { key: 'owner', label: 'Owner', type: 'person', icon: '👤' },
2705
+ { key: 'due', label: 'Due', type: 'date', icon: '📅' },
2706
+ { key: 'progress', label: 'Progress', type: 'progress', icon: '▰' },
2707
+ { key: 'effort', label: 'Effort (h)', type: 'number', icon: '#', locale: 'en', format: { minimumFractionDigits: 1 } },
2708
+ ];
2709
+ db.rows = [
2710
+ { id: 1, icon: '🧭', name: 'App shell & sidebar', status: 'done', owner: { name: 'Marcel', initials: 'MK', color: 'purple' }, due: '08.05.2026', progress: 100, effort: 6 },
2711
+ { id: 2, icon: '📄', name: 'Page shell & typography', status: 'done', owner: { name: 'Marcel', initials: 'MK', color: 'purple' }, due: '10.05.2026', progress: 100, effort: 4.5 },
2712
+ { id: 3, icon: '🗃️', name: 'Database table view', status: 'progress', owner: { name: 'Marcel', initials: 'MK', color: 'purple' }, due: '20.05.2026', progress: 65, effort: 12.5 },
2713
+ { id: 4, icon: '▤', name: 'Board view & drag-and-drop', status: 'planned', due: '02.06.2026', progress: 0, effort: 8 },
2714
+ ];
2715
+ }</script>
2716
+ ```
2717
+
2718
+ | Attribute | Type | Default | Description |
2719
+ |---|---|---|---|
2720
+ | `name` | string | – | View name. |
2721
+ | `label` | string | – | Tab label. |
2722
+ | `group-by` | string | – | Select column key. |
2723
+ | `title-key` | string | – | Card title column. |
2724
+ | `meta-keys` | list | – | Comma-separated meta columns. |
2725
+ | `new-row` | boolean | – | Show + per column. |
2726
+
2727
+ **Events:** `nk-select` `{ row, id }` – Card clicked. · `nk-change` `{ row, key, value }` – Card dropped into another column. · `nk-action` `{ action: 'new-row', value }` – + clicked (value = column).
2728
+
2729
+ **Properties:** `columns`, `rows`, `data` · **Methods:** `move(id, value)`, `refresh()`
2730
+
2731
+ **Replaces:** `.nk-board`, `.active`, `.nk-board-col`, `.nk-board-col-header`, `.count`, `.nk-card`, `.card-title`, `.card-meta`
2732
+
2733
+ ```html
2734
+ <!-- equivalent class markup -->
2735
+ <div class="nk-board active">
2736
+ <div class="nk-board-col"><div class="nk-board-col-header"><span class="nk-tag orange">Planned</span><span class="count">1</span></div><div class="nk-card" draggable="true"><div class="card-title">▤ Board view & drag-and-drop</div><div class="card-meta"><span>📅 02.06.2026</span><span>▰ 0%</span></div></div><div class="nk-new-row" style="padding:6px 10px">+</div></div>
2737
+ <div class="nk-board-col"><div class="nk-board-col-header"><span class="nk-tag blue">In progress</span><span class="count">1</span></div><div class="nk-card" draggable="true"><div class="card-title">🗃️ Database table view</div><div class="card-meta"><span>📅 20.05.2026</span><span>▰ 65%</span></div></div><div class="nk-new-row" style="padding:6px 10px">+</div></div>
2738
+ <div class="nk-board-col"><div class="nk-board-col-header"><span class="nk-tag green">Done</span><span class="count">2</span></div><div class="nk-card" draggable="true"><div class="card-title">🧭 App shell & sidebar</div><div class="card-meta"><span>📅 08.05.2026</span><span>▰ 100%</span></div></div><div class="nk-card" draggable="true"><div class="card-title">📄 Page shell & typography</div><div class="card-meta"><span>📅 10.05.2026</span><span>▰ 100%</span></div></div><div class="nk-new-row" style="padding:6px 10px">+</div></div>
2739
+ </div>
2740
+ ```
2741
+
2742
+ **Small screens:** Columns scroll horizontally.
2743
+
2744
+ ### 3.80 `<nk-list-view>` – List view
2745
+
2746
+ The third database view: one line per row – icon and title, the `meta-keys` on the right (default: the select and date columns, in column order). Dates and text stand as text, selects as tags, a person as avatar and name. Rows fire `nk-select`; `new-row` adds the add row.
2747
+
2748
+ ```html
2749
+ <nk-list-view meta-keys="due,status"></nk-list-view>
2750
+ <script>{
2751
+ const db = document.currentScript.previousElementSibling;
2752
+ db.columns = [
2753
+ { key: 'name', label: 'Name', type: 'text', icon: '📄', title: true },
2754
+ { key: 'status', label: 'Status', type: 'select', icon: '◉', options: [
2755
+ { value: 'planned', label: 'Planned', color: 'orange' }, { value: 'progress', label: 'In progress', color: 'blue' }, { value: 'done', label: 'Done', color: 'green' } ] },
2756
+ { key: 'owner', label: 'Owner', type: 'person', icon: '👤' },
2757
+ { key: 'due', label: 'Due', type: 'date', icon: '📅' },
2758
+ { key: 'progress', label: 'Progress', type: 'progress', icon: '▰' },
2759
+ { key: 'effort', label: 'Effort (h)', type: 'number', icon: '#', locale: 'en', format: { minimumFractionDigits: 1 } },
2760
+ ];
2761
+ db.rows = [
2762
+ { id: 1, icon: '🧭', name: 'App shell & sidebar', status: 'done', owner: { name: 'Marcel', initials: 'MK', color: 'purple' }, due: '08.05.2026', progress: 100, effort: 6 },
2763
+ { id: 2, icon: '📄', name: 'Page shell & typography', status: 'done', owner: { name: 'Marcel', initials: 'MK', color: 'purple' }, due: '10.05.2026', progress: 100, effort: 4.5 },
2764
+ { id: 3, icon: '🗃️', name: 'Database table view', status: 'progress', owner: { name: 'Marcel', initials: 'MK', color: 'purple' }, due: '20.05.2026', progress: 65, effort: 12.5 },
2765
+ { id: 4, icon: '▤', name: 'Board view & drag-and-drop', status: 'planned', due: '02.06.2026', progress: 0, effort: 8 },
2766
+ ];
2767
+ }</script>
2768
+ ```
2769
+
2770
+ | Attribute | Type | Default | Description |
2771
+ |---|---|---|---|
2772
+ | `name` | string | – | View name. |
2773
+ | `label` | string | – | Tab label. |
2774
+ | `title-key` | string | – | Title column. |
2775
+ | `meta-keys` | list | – | Comma-separated columns on the right. |
2776
+ | `new-row` | boolean | – | Show the add row. |
2777
+ | `new-row-label` | string | `+ New page` | Its text. |
2778
+
2779
+ **Events:** `nk-select` `{ row, id }` – Row clicked or Enter. · `nk-action` `{ action: 'new-row' }` – Add row clicked.
2780
+
2781
+ **Properties:** `columns`, `rows`, `data` · **Methods:** `refresh()`
2782
+
2783
+ **Replaces:** `.nk-list`, `.nk-list-item`, `.l-icon`, `.l-title`, `.l-meta`
2784
+
2785
+ ```html
2786
+ <!-- equivalent class markup -->
2787
+ <div class="nk-list">
2788
+ <div class="nk-list-item"><span class="l-icon">🧭</span><span class="l-title">App shell & sidebar</span><span class="l-meta">08.05.2026<span class="nk-tag green">Done</span></span></div>
2789
+ <div class="nk-list-item"><span class="l-icon">📄</span><span class="l-title">Page shell & typography</span><span class="l-meta">10.05.2026<span class="nk-tag green">Done</span></span></div>
2790
+ <div class="nk-list-item"><span class="l-icon">🗃️</span><span class="l-title">Database table view</span><span class="l-meta">20.05.2026<span class="nk-tag blue">In progress</span></span></div>
2791
+ <div class="nk-list-item"><span class="l-icon">▤</span><span class="l-title">Board view & drag-and-drop</span><span class="l-meta">02.06.2026<span class="nk-tag orange">Planned</span></span></div>
2792
+ </div>
2793
+ ```
2794
+
2795
+ **Small screens:** Stays one line per row: the title ends in an ellipsis, the properties keep their place.
2796
+
2797
+ ### 3.81 `<nk-calendar-view>` – Calendar view
2798
+
2799
+ The fourth database view (NotionKit 1.9.0): a month, the rows as cards on their dates. `date-key` names the date column (default: the first one) – `YYYY-MM-DD` or `D.M.YYYY`, a range on its start. Today sits on a red pill, days of other months are washed; `weeks` puts the calendar week in front, `weekend` washes the days not worked. A card fires `nk-select` like a row of the table; Today and ‹ › change the month (`nk-month`). In `nk-database` it is a tab like the others and shows the same rows.
2800
+
2801
+ ```html
2802
+ <nk-calendar-view date-key="due" month="2026-05" today="2026-05-20" weeks week-start="1" today-label="Today"></nk-calendar-view>
2803
+ <script>{
2804
+ const db = document.currentScript.previousElementSibling;
2805
+ db.columns = [
2806
+ { key: 'name', label: 'Name', type: 'text', icon: '📄', title: true },
2807
+ { key: 'status', label: 'Status', type: 'select', icon: '◉', options: [
2808
+ { value: 'planned', label: 'Planned', color: 'orange' }, { value: 'progress', label: 'In progress', color: 'blue' }, { value: 'done', label: 'Done', color: 'green' } ] },
2809
+ { key: 'owner', label: 'Owner', type: 'person', icon: '👤' },
2810
+ { key: 'due', label: 'Due', type: 'date', icon: '📅' },
2811
+ { key: 'progress', label: 'Progress', type: 'progress', icon: '▰' },
2812
+ { key: 'effort', label: 'Effort (h)', type: 'number', icon: '#', locale: 'en', format: { minimumFractionDigits: 1 } },
2813
+ ];
2814
+ db.rows = [
2815
+ { id: 1, icon: '🧭', name: 'App shell & sidebar', status: 'done', owner: { name: 'Marcel', initials: 'MK', color: 'purple' }, due: '08.05.2026', progress: 100, effort: 6 },
2816
+ { id: 2, icon: '📄', name: 'Page shell & typography', status: 'done', owner: { name: 'Marcel', initials: 'MK', color: 'purple' }, due: '10.05.2026', progress: 100, effort: 4.5 },
2817
+ { id: 3, icon: '🗃️', name: 'Database table view', status: 'progress', owner: { name: 'Marcel', initials: 'MK', color: 'purple' }, due: '20.05.2026', progress: 65, effort: 12.5 },
2818
+ { id: 4, icon: '▤', name: 'Board view & drag-and-drop', status: 'planned', due: '02.06.2026', progress: 0, effort: 8 },
2819
+ ];
2820
+ }</script>
2821
+ ```
2822
+
2823
+ | Attribute | Type | Default | Description |
2824
+ |---|---|---|---|
2825
+ | `name` | string | `calendar` | View name. |
2826
+ | `label` | string | – | Tab label. |
2827
+ | `date-key` | string | – | Date column. |
2828
+ | `title-key` | string | – | Title column. |
2829
+ | `month` | YYYY-MM | – | The month shown; default today’s. |
2830
+ | `weeks` | boolean | – | The ISO calendar week in front of each row. |
2831
+ | `week-start` | 0–6 | `Intl` | First day of the week: 0 Sunday, 1 Monday … |
2832
+ | `weekend` | list | – | Weekdays not worked, washed – `6,0`. |
2833
+ | `today` | YYYY-MM-DD | – | Another today – for tests and docs. |
2834
+ | `locale` | BCP 47 | `lang` | Language of the names and the week. |
2835
+ | `today-label` | string | `Today` | Today button. |
2836
+ | `prev-label` | string | `Previous month` | Names ‹. |
2837
+ | `next-label` | string | `Next month` | Names ›. |
2838
+ | `week-label` | string | `W · KW` | Head of the week column. |
2839
+
2840
+ **Events:** `nk-select` `{ row, id }` – Card clicked or Enter. · `nk-month` `{ month }` – Another month shown.
2841
+
2842
+ **Properties:** `columns`, `rows`, `data`, `month` · **Methods:** `refresh()`
2843
+
2844
+ **Replaces:** `.nk-calendar-view`, `.weeks`, `.cv-head`, `.cv-title`, `.cal-nav`, `.cv-grid`, `.cv-wd`, `.cv-week`, `.cv-day`, `.out`, `.off`, `.today`, `.cv-num`, `.cv-item`
2845
+
2846
+ ```html
2847
+ <!-- equivalent class markup -->
2848
+ <div class="nk-calendar-view weeks">
2849
+ <div class="cv-head"><div class="cv-title">May 2026</div><button class="cal-nav">Today</button><button class="cal-nav" aria-label="Previous month"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M15 6l-6 6 6 6"/></svg></button><button class="cal-nav" aria-label="Next month"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M9 6l6 6-6 6"/></svg></button></div>
2850
+ <div class="cv-grid">
2851
+ <div class="cv-wd">W</div><div class="cv-wd">Mon</div><div class="cv-wd">Tue</div><div class="cv-wd">Wed</div><div class="cv-wd">Thu</div><div class="cv-wd">Fri</div><div class="cv-wd">Sat</div><div class="cv-wd">Sun</div>
2852
+ <div class="cv-week">18</div><div class="cv-day out"><span class="cv-num">27</span></div><div class="cv-day out"><span class="cv-num">28</span></div><div class="cv-day out"><span class="cv-num">29</span></div><div class="cv-day out"><span class="cv-num">30</span></div><div class="cv-day"><span class="cv-num">1</span></div><div class="cv-day"><span class="cv-num">2</span></div><div class="cv-day"><span class="cv-num">3</span></div>
2853
+ <div class="cv-week">19</div><div class="cv-day"><span class="cv-num">4</span></div><div class="cv-day"><span class="cv-num">5</span></div><div class="cv-day"><span class="cv-num">6</span></div><div class="cv-day"><span class="cv-num">7</span></div><div class="cv-day"><span class="cv-num">8</span><button class="cv-item">🧭 App shell & sidebar</button></div><div class="cv-day"><span class="cv-num">9</span></div><div class="cv-day"><span class="cv-num">10</span><button class="cv-item">📄 Page shell & typography</button></div>
2854
+ <div class="cv-week">20</div><div class="cv-day"><span class="cv-num">11</span></div><div class="cv-day"><span class="cv-num">12</span></div><div class="cv-day"><span class="cv-num">13</span></div><div class="cv-day"><span class="cv-num">14</span></div><div class="cv-day"><span class="cv-num">15</span></div><div class="cv-day"><span class="cv-num">16</span></div><div class="cv-day"><span class="cv-num">17</span></div>
2855
+ <div class="cv-week">21</div><div class="cv-day"><span class="cv-num">18</span></div><div class="cv-day"><span class="cv-num">19</span></div><div class="cv-day today"><span class="cv-num">20</span><button class="cv-item">🗃️ Database table view</button></div><div class="cv-day"><span class="cv-num">21</span></div><div class="cv-day"><span class="cv-num">22</span></div><div class="cv-day"><span class="cv-num">23</span></div><div class="cv-day"><span class="cv-num">24</span></div>
2856
+ <div class="cv-week">22</div><div class="cv-day"><span class="cv-num">25</span></div><div class="cv-day"><span class="cv-num">26</span></div><div class="cv-day"><span class="cv-num">27</span></div><div class="cv-day"><span class="cv-num">28</span></div><div class="cv-day"><span class="cv-num">29</span></div><div class="cv-day"><span class="cv-num">30</span></div><div class="cv-day"><span class="cv-num">31</span></div>
2857
+ </div>
2858
+ </div>
2859
+ ```
2860
+
2861
+ **Small screens:** Keeps seven columns; the days get lower (64px) and the cards smaller.
2862
+
2863
+ ### 3.82 `<nk-filter-bar>` – Filter bar
2864
+
2865
+ The filters in effect as NotionKit’s filter pills – `.active` with an accent tint, a × to remove each, a quiet `add` pill at the end – with no inline style. In `slot="filters"` of `nk-database` the row sits under the toolbar. A pill’s label fires `nk-action { action: "edit" }`, the add pill `{ action: "add" }`, each with the clicked button as `anchor` for `menu.show(anchor)`. `bar.apply(rows)` keeps rows where every filter matches by strict equality (`row[key] === value`, so use the option value) – or differs with `op: "is-not"` – and the search text appears in any string field (a person’s `name`); the data logic stays yours. Its own Filter and Sort tools and the search field are there for a bar without a database toolbar. It keeps its own copy of `filters`: the array you pass is never changed, a removed pill is reported in `nk-change`.
2866
+
2867
+ ```html
2868
+ <nk-filter-bar add no-filter no-sort remove-label="Remove filter"></nk-filter-bar>
2869
+ <script>{ document.currentScript.previousElementSibling.filters = [{ key: 'status', value: 'done', op: 'is-not', label: 'Status: Open' }]; }</script>
2870
+ ```
2871
+
2872
+ | Attribute | Type | Default | Description |
2873
+ |---|---|---|---|
2874
+ | `add` | boolean | – | Show the add pill. |
2875
+ | `add-label` | string | `+ Filter` | Its text. |
2876
+ | `no-filter` | boolean | – | Hide the Filter tool. |
2877
+ | `no-sort` | boolean | – | Hide the Sort tool. |
2878
+ | `filter-label` | string | `Filter` | Text of the Filter tool. |
2879
+ | `sort-label` | string | `Sort` | Text of the Sort tool. |
2880
+ | `remove-label` | string | `Remove filter` | The ×’s name, followed by the pill’s text. |
2881
+ | `search` | boolean | – | Show the search field. |
2882
+ | `placeholder` | string | – | Search placeholder. |
2883
+
2884
+ **Slots:** `(default)` – Extra pills or controls between the pills and the search.
2885
+
2886
+ **Events:** `nk-change` `{ filters, search }` – A filter removed or the search typed. · `nk-action` `{ action: 'edit' | 'add' | 'filter' | 'sort', index?, filter?, anchor }` – A pill or a tool clicked.
2887
+
2888
+ **Properties:** `filters`, `value` · **Methods:** `apply(rows)`
2889
+
2890
+ **Replaces:** `.nk-filter-row`, `.nk-filter-pill`, `.active`, `.add`, `.fp-remove`, `.nk-db-tool`, `.nk-input`
2891
+
2892
+ ```html
2893
+ <!-- equivalent class markup -->
2894
+ <div class="nk-filter-row"><span class="nk-filter-pill active"><button>Status: Open</button><button class="fp-remove" aria-label="Remove filter">×</button></span><button class="nk-filter-pill add">+ Filter</button></div>
2895
+ ```
2896
+
2897
+ **Small screens:** The pills wrap onto further rows.
2898
+
2899
+ ### 3.83 `<nk-comments>` – Comment thread
2900
+
2901
+ A left-ruled thread of `nk-comment`s with an input row. Enter or the button fires `nk-submit { text }`; appending the new comment is yours.
2902
+
2903
+ ```html
2904
+ <nk-comments placeholder="Comment …" send-label="Send">
2905
+ <nk-comment author="Sara Lindt" time="1 hr ago" color="#448361">The board view already feels very close to the original. 👍</nk-comment>
2906
+ <nk-comment author="Mona" time="20 min ago" avatar="✨" color="var(--nk-text-tertiary)"><span slot="head" class="nk-tag blue" style="font-size:10.5px">AI</span>I have flagged the overdue entries and prepared a summary.</nk-comment>
2907
+ </nk-comments>
2908
+ ```
2909
+
2910
+ | Attribute | Type | Default | Description |
2911
+ |---|---|---|---|
2912
+ | `placeholder` | string | – | Input placeholder. |
2913
+ | `send-label` | string | `Send` | Button text. |
2914
+ | `no-input` | boolean | – | Read-only thread. |
2915
+ | `disabled` | boolean | – | Input disabled. |
2916
+
2917
+ **Slots:** `(default)` – `nk-comment` children.
2918
+
2919
+ **Events:** `nk-submit` `{ text }` – New comment typed; `preventDefault()` keeps the text.
2920
+
2921
+ **Properties:** `value` · **Methods:** `submit()`, `focus()`
2922
+
2923
+ **Replaces:** `.nk-comments`, `.nk-comment`, `.mini-avatar`, `.c-head`, `.c-body`, `.nk-comment-input`
2924
+
2925
+ ```html
2926
+ <!-- equivalent class markup -->
2927
+ <div class="nk-comments">
2928
+ <div class="nk-comment"><span class="mini-avatar" style="background:#448361">SL</span><div><div class="c-head"><b>Sara Lindt</b> · 1 hr ago</div><div class="c-body">The board view already feels very close to the original. 👍</div></div></div>
2929
+ <div class="nk-comment"><span class="mini-avatar" style="background:var(--nk-text-tertiary)">✨</span><div><div class="c-head"><b>Mona</b><span class="nk-tag blue" style="font-size:10.5px">AI</span> · 20 min ago</div><div class="c-body">I have flagged the overdue entries and prepared a summary.</div></div></div>
2930
+ <div class="nk-comment-input"><input class="nk-input" placeholder="Comment …"><button class="nk-btn primary small">Send</button></div>
2931
+ </div>
2932
+ ```
2933
+
2934
+ **Small screens:** Unchanged.
2935
+
2936
+ ### 3.84 `<nk-comment>` – Comment
2937
+
2938
+ One comment: avatar (initials + `color`), bold author, time, body. `slot="head"` adds content after the name.
2939
+
2940
+ ```html
2941
+ <nk-comments no-input><nk-comment author="Sara Lindt" time="1 hr ago" color="green">The board view already feels very close to the original. 👍</nk-comment></nk-comments>
2942
+ ```
2943
+
2944
+ | Attribute | Type | Default | Description |
2945
+ |---|---|---|---|
2946
+ | `author` | string | – | Name. |
2947
+ | `time` | string | – | Relative time. |
2948
+ | `avatar` | string | – | Initials/emoji (default: from the author). |
2949
+ | `color` | colour name | CSS | – | Avatar colour: one of the nine names or any CSS background; without it the avatar gradient. |
2950
+
2951
+ **Slots:** `(default)` – Body. · `head` – After the name (tag, badge). · `avatar` – Custom avatar.
2952
+
2953
+ **Replaces:** `.nk-comment`, `.c-head`, `.c-body`
2954
+
2955
+ ```html
2956
+ <!-- equivalent class markup -->
2957
+ <div class="nk-comments"><div class="nk-comment"><span class="nk-avatar green">SL</span><div><div class="c-head"><b>Sara Lindt</b> · 1 hr ago</div><div class="c-body">The board view already feels very close to the original. 👍</div></div></div></div>
2958
+ ```
2959
+
2960
+ **Small screens:** Unchanged.
2961
+
2962
+ ### 3.85 `<nk-ai-thread>` – AI thread
2963
+
2964
+ The conversation column: `nk-ai-msg` children (`role="user"` gets the gradient avatar), followed by an `nk-ai-input-row`. Action buttons in `slot="actions"` fire `nk-action { action, value }` – both carry the button’s `value` (or its text).
2965
+
2966
+ ```html
2967
+ <nk-ai-thread>
2968
+ <nk-ai-msg role="user" name="You" avatar="MK">Summarise the open tasks for this project.</nk-ai-msg>
2969
+ <nk-ai-msg role="assistant" name="Mona" badge="· AI">Two tasks are open: the <b>table view</b> sits at 65 % (due 20 May), the <b>board with drag and drop</b> is planned.
2970
+ <button slot="actions" value="copy">📋 Copy</button><button slot="actions" value="rephrase">↻ Rephrase</button><button slot="actions" value="like">👍</button>
2971
+ </nk-ai-msg>
2972
+ </nk-ai-thread>
2973
+ <nk-ai-input-row placeholder="Ask Mona something …"></nk-ai-input-row>
2974
+ ```
2975
+
2976
+ _No attributes._
2977
+
2978
+ **Slots:** `(default)` – `nk-ai-msg` children.
2979
+
2980
+ **Events:** `nk-action` `{ action }` – Action button of a message.
2981
+
2982
+ **Replaces:** `.nk-ai-thread`, `.nk-ai-msg`, `.user`, `.a-body`, `.a-name`, `.nk-ai-actions`, `.nk-ai-input-row`, `.nk-ai-send`
2983
+
2984
+ ```html
2985
+ <!-- equivalent class markup -->
2986
+ <div class="nk-ai-thread">
2987
+ <div class="nk-ai-msg user"><span class="mini-avatar">MK</span><div class="a-body"><div class="a-name">You</div>Summarise the open tasks for this project.</div></div>
2988
+ <div class="nk-ai-msg"><span class="mini-avatar">✨</span><div class="a-body"><div class="a-name">Mona <span>· AI</span></div>Two tasks are open: the <b>table view</b> sits at 65 % (due 20 May), the <b>board with drag and drop</b> is planned.<div class="nk-ai-actions"><button>📋 Copy</button><button>↻ Rephrase</button><button>👍</button></div></div></div>
2989
+ </div>
2990
+ <div class="nk-ai-input-row"><span style="font-size:14px">✨</span><input placeholder="Ask Mona something …"><button class="nk-ai-send">↑</button></div>
2991
+ ```
2992
+
2993
+ **Small screens:** Unchanged.
2994
+
2995
+ ### 3.86 `<nk-ai-msg>` – AI message
2996
+
2997
+ One message. `role="user"` flips the avatar to the gradient; `badge` is the grey suffix after the name (“· AI”); plain `<button slot="actions">`s form the action row.
2998
+
2999
+ ```html
3000
+ <nk-ai-thread><nk-ai-msg role="user" bubble>What is still open on this page?</nk-ai-msg><nk-ai-msg role="assistant" name="Mona" badge="· AI">Two tasks are open: the <b>table view</b> sits at 65 % (due 20 May), the <b>board with drag and drop</b> is planned.<button slot="actions" value="copy">📋 Copy</button></nk-ai-msg></nk-ai-thread>
3001
+ ```
3002
+
3003
+ | Attribute | Type | Default | Description |
3004
+ |---|---|---|---|
3005
+ | `role` | user | assistant | `assistant` | Who speaks. |
3006
+ | `name` | string | – | Name line. |
3007
+ | `badge` | string | – | Grey suffix. |
3008
+ | `avatar` | string | – | Initials/emoji. |
3009
+ | `color` | CSS color | – | Avatar background override. |
3010
+ | `bubble` | boolean | – | The message as a grey bubble without avatar or name; with `role="user"` on the right, as Notion’s AI chat shows your own question. |
3011
+
3012
+ **Slots:** `(default)` – Message body (HTML allowed). · `actions` – `<button value>` children. · `avatar` – Custom avatar.
3013
+
3014
+ **Events:** `nk-action` `{ action, value }` – Action button clicked.
3015
+
3016
+ **Replaces:** `.nk-ai-msg`, `.user`, `.bubble`, `.a-body`, `.a-name`, `.nk-ai-actions`
3017
+
3018
+ ```html
3019
+ <!-- equivalent class markup -->
3020
+ <div class="nk-ai-thread"><div class="nk-ai-msg user bubble"><div class="a-body">What is still open on this page?</div></div><div class="nk-ai-msg"><span class="mini-avatar">✨</span><div class="a-body"><div class="a-name">Mona <span>· AI</span></div>Two tasks are open: the <b>table view</b> sits at 65 % (due 20 May), the <b>board with drag and drop</b> is planned.<div class="nk-ai-actions"><button>📋 Copy</button></div></div></div></div>
3021
+ ```
3022
+
3023
+ **Small screens:** Unchanged.
3024
+
3025
+ ### 3.87 `<nk-ai-input-row>` – AI input row
3026
+
3027
+ The prompt field with ✨ and a send button. Enter or the button fires `nk-submit { text }` and clears the field.
3028
+
3029
+ ```html
3030
+ <nk-ai-input-row placeholder="Ask Mona something …"></nk-ai-input-row>
3031
+ ```
3032
+
3033
+ | Attribute | Type | Default | Description |
3034
+ |---|---|---|---|
3035
+ | `placeholder` | string | – | Placeholder. |
3036
+ | `value` | string | – | Preset text. |
3037
+ | `disabled` | boolean | – | Disabled while the assistant answers. |
3038
+ | `icon` | string | `✨` | Leading glyph. |
3039
+
3040
+ **Events:** `nk-submit` `{ text }` – Prompt sent.
3041
+
3042
+ **Properties:** `value` · **Methods:** `submit()`, `focus()`
3043
+
3044
+ **Replaces:** `.nk-ai-input-row`, `.nk-ai-send`
3045
+
3046
+ ```html
3047
+ <!-- equivalent class markup -->
3048
+ <div class="nk-ai-input-row"><span style="font-size:14px">✨</span><input placeholder="Ask Mona something …"><button class="nk-ai-send">↑</button></div>
3049
+ ```
3050
+
3051
+ **Small screens:** Unchanged.
3052
+
3053
+
3054
+ # 4. Composition Patterns (app skeletons)
3055
+
3056
+ Eight skeletons, one per app shape, mirroring the NotionKit CSS SKILL.md. Copy one, delete what you do not need.
3057
+
3058
+ ## 4.1 Workspace app
3059
+
3060
+ **When:** the default for Notion-like document apps – pages are the primary object, a tree on the left, one page on the right. A page that is a database row shows its properties under the title and lists its sub-pages.
3061
+
3062
+ ```html
3063
+ <!DOCTYPE html>
3064
+ <html lang="en" data-theme="light">
3065
+ <head>
3066
+ <meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
3067
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit@1.11.1/notionkit.min.css">
3068
+ <script src="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit-elements@1.11.1/dist/notionkit-elements.min.js"></script>
3069
+ </head>
3070
+ <body class="nk-body">
3071
+ <nk-app>
3072
+ <!-- collapsible: the « collapses it on the desktop, the ☰ and ⌘\ bring it back -->
3073
+ <nk-sidebar slot="sidebar" id="sidebar" collapsible>
3074
+ <nk-workspace-switcher slot="workspace" name="MonaHilft"></nk-workspace-switcher>
3075
+ <nk-tree id="tree">
3076
+ <nk-tree-item icon="🔍" value="search" no-actions>Search<span slot="end" class="nk-kbd-hint"><nk-kbd>⌘</nk-kbd><nk-kbd>K</nk-kbd></span></nk-tree-item>
3077
+ <nk-tree-item icon="🏠" value="home">Home</nk-tree-item>
3078
+ <nk-tree-item icon="📥" value="inbox">Inbox</nk-tree-item>
3079
+ <nk-section-label addable>Favourites</nk-section-label>
3080
+ <nk-tree-item icon="📊" value="overview" open>Project overview
3081
+ <nk-tree-item icon="🚀" value="mvp" active>NotionKit MVP</nk-tree-item>
3082
+ <nk-tree-item icon="🎙️" value="voh">Voice-Office-Hub</nk-tree-item>
3083
+ </nk-tree-item>
3084
+ <nk-section-label addable>Workspace</nk-section-label>
3085
+ <nk-tree-item icon="🧠" value="kb">Knowledge base
3086
+ <nk-tree-item icon="📄" value="onboarding">Onboarding</nk-tree-item>
3087
+ </nk-tree-item>
3088
+ <nk-tree-item icon="🎨" value="design">Design system</nk-tree-item>
3089
+ </nk-tree>
3090
+ <nk-tree-item slot="footer" icon="⚙️" value="settings" no-actions>Settings</nk-tree-item>
3091
+ <nk-tree-item slot="footer" icon="🗑️" value="trash" no-actions>Trash</nk-tree-item>
3092
+ </nk-sidebar>
3093
+
3094
+ <nk-topbar>
3095
+ <nk-btn variant="sidebar" aria-label="Menu" data-tooltip="Open sidebar">☰</nk-btn>
3096
+ <nk-breadcrumb><span>📊 Project overview</span><span>🚀 NotionKit MVP</span></nk-breadcrumb>
3097
+ <nk-btn slot="actions" variant="share">Share</nk-btn>
3098
+ <nk-theme-toggle slot="actions"></nk-theme-toggle>
3099
+ </nk-topbar>
3100
+
3101
+ <!-- Page options: add full (full width) or small (14px text) to <nk-page>. -->
3102
+ <nk-page icon="🚀" cover>
3103
+ <nk-page-title editable>NotionKit MVP</nk-page-title>
3104
+ <nk-props>
3105
+ <nk-prop label="Status" icon="◉"><nk-tag color="blue">In progress</nk-tag></nk-prop>
3106
+ <nk-prop label="Owner" icon="👤"><nk-avatar size="small">AL</nk-avatar>Ada Lovelace</nk-prop>
3107
+ <nk-prop label="Due" icon="📅">2 June 2026</nk-prop>
3108
+ </nk-props>
3109
+ <p class="lead">A calm, document-centric workspace app – built from elements only.</p>
3110
+ <nk-heading>Sub-pages</nk-heading>
3111
+ <nk-list-view id="subpages" meta-keys="due,status"></nk-list-view>
3112
+ <!-- The editor: mount TipTap into a light-DOM .nk-block-host (docs-editor.js).
3113
+ Saved HTML shown read-only goes into <div class="nk-prose"> – same look. -->
3114
+ <div class="nk-block-host" id="editor"></div>
3115
+ </nk-page>
3116
+
3117
+ <nk-tab-bar>
3118
+ <nk-tab-bar-item icon="🏠" value="home" active>Home</nk-tab-bar-item>
3119
+ <nk-tab-bar-item icon="📥" value="inbox">Inbox</nk-tab-bar-item>
3120
+ <nk-tab-bar-item icon="🔍" value="search">Search</nk-tab-bar-item>
3121
+ <nk-tab-bar-item icon="☰" drawer>More</nk-tab-bar-item>
3122
+ </nk-tab-bar>
3123
+ </nk-app>
3124
+ <script>
3125
+ tree.addEventListener('nk-select', e => console.log('open page', e.detail.value));
3126
+ tree.addEventListener('nk-action', e => console.log(e.detail.action, 'on', e.detail.value));
3127
+ subpages.columns = [
3128
+ { key: 'name', label: 'Name', title: true }, { key: 'due', label: 'Due', type: 'date' },
3129
+ { key: 'status', label: 'Status', type: 'select', options: [{ value: 'done', label: 'Done', color: 'green' }, { value: 'open', label: 'Open', color: 'gray' }] },
3130
+ ];
3131
+ subpages.rows = [{ id: 1, icon: '📄', name: 'Hiring plan', due: '12 Aug', status: 'done' }, { id: 2, icon: '📄', name: 'Budget review', due: '2 Sep', status: 'open' }];
3132
+ subpages.addEventListener('nk-select', e => console.log('open sub-page', e.detail.id));
3133
+ </script>
3134
+ </body>
3135
+ </html>
3136
+ ```
3137
+
3138
+ Rules of the shell: `nk-sidebar`, `nk-topbar` and `nk-page` are `display: contents` hosts – their inner boxes are direct flex children of `.nk-app` / `.nk-main`, so do not style the hosts. `<nk-btn variant="sidebar">` is the ☰: shown below 860px only, where the sidebar is hidden, it opens it as a drawer – NotionKit’s own drawer rules, no script. For phones and installed PWAs add `<nk-tab-bar>` as the last child of `<nk-app>`: it lands below the page in the main column, is hidden above 860px (the sidebar is the navigation there) and shown below; a `drawer` item opens the sidebar. For Notion’s “More” – the rest of the sidebar as a list from the bottom edge – put an `<nk-sheet>` under `<body>` and open it from the item’s `nk-select` after `e.preventDefault()`. Never give the bar a `view-transition-name` – it stays put between pages.
3139
+
3140
+ ## 4.2 Database app
3141
+
3142
+ **When:** structured, data-centric apps – a CRM, a tracker, an editorial calendar. Rows are the primary object; the database is the main room.
3143
+
3144
+ ```html
3145
+ <!DOCTYPE html>
3146
+ <html lang="en" data-theme="light">
3147
+ <head>
3148
+ <meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
3149
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit@1.11.1/notionkit.min.css">
3150
+ <script src="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit-elements@1.11.1/dist/notionkit-elements.min.js"></script>
3151
+ </head>
3152
+ <body class="nk-body">
3153
+ <nk-app>
3154
+ <nk-sidebar slot="sidebar">
3155
+ <nk-workspace-switcher slot="workspace" name="MonaHilft"></nk-workspace-switcher>
3156
+ <nk-tree>
3157
+ <nk-section-label addable>Databases</nk-section-label>
3158
+ <nk-tree-item icon="🗃️" value="projects" active>Projects</nk-tree-item>
3159
+ <nk-tree-item icon="🤝" value="clients">Clients</nk-tree-item>
3160
+ <nk-tree-item icon="🗓️" value="editorial">Editorial plan</nk-tree-item>
3161
+ </nk-tree>
3162
+ </nk-sidebar>
3163
+ <nk-topbar>
3164
+ <nk-breadcrumb><span>🗃️ Projects</span></nk-breadcrumb>
3165
+ <nk-btn slot="actions" variant="share">Share</nk-btn>
3166
+ <nk-theme-toggle slot="actions"></nk-theme-toggle>
3167
+ </nk-topbar>
3168
+ <nk-page icon="🗃️">
3169
+ <nk-page-title>Projects</nk-page-title>
3170
+ <nk-database id="db" view="table" add-view>
3171
+ <nk-btn slot="tools" variant="tool" id="filterBtn" aria-haspopup="menu">Filter</nk-btn>
3172
+ <nk-btn slot="tools" variant="primary" small id="newBtn">New</nk-btn>
3173
+ <nk-filter-bar slot="filters" id="filters" add no-filter no-sort></nk-filter-bar>
3174
+ <nk-table-view name="table" label="▦ Table" count new-row sortable></nk-table-view>
3175
+ <nk-board-view name="board" label="▤ Board" group-by="status" new-row></nk-board-view>
3176
+ <nk-calendar-view name="calendar" label="📅 Calendar" date-key="due" weeks></nk-calendar-view>
3177
+ </nk-database>
3178
+ </nk-page>
3179
+ </nk-app>
3180
+ <!-- a popover on the desktop, a bottom sheet on a phone -->
3181
+ <nk-menu floating sheet id="filterMenu">
3182
+ <nk-menu-item type="label">Filter by</nk-menu-item>
3183
+ <nk-menu-item type="check" icon="◉" value="done">Status: Done</nk-menu-item>
3184
+ </nk-menu>
3185
+ <!-- one date picker for every date: a popover under the cell, a sheet on a phone -->
3186
+ <nk-calendar floating sheet weeks weekend="6,0" id="picker"></nk-calendar>
3187
+ <!-- a row beside the table, a sheet on a phone -->
3188
+ <nk-peek id="peek">
3189
+ <nk-page-title id="peekTitle"></nk-page-title>
3190
+ <div class="nk-prose"><p>Notes on this row – the page behind it.</p></div>
3191
+ </nk-peek>
3192
+ <nk-toast id="toast"></nk-toast>
3193
+ <script>
3194
+ const columns = [
3195
+ { key: 'name', label: 'Name', type: 'text', icon: '📄', title: true },
3196
+ { key: 'status', label: 'Status', type: 'select', icon: '◉', options: [
3197
+ { value: 'planned', label: 'Planned', color: 'orange' },
3198
+ { value: 'progress', label: 'In progress', color: 'blue' },
3199
+ { value: 'done', label: 'Done', color: 'green' } ] },
3200
+ { key: 'owner', label: 'Owner', type: 'person', icon: '👤' },
3201
+ { key: 'due', label: 'Due', type: 'date', icon: '📅' },
3202
+ { key: 'progress', label: 'Progress', type: 'progress', icon: '▰' },
3203
+ ];
3204
+ const rows = [
3205
+ { id: 1, icon: '🧭', name: 'App shell & sidebar', status: 'done', owner: { name: 'Marcel', initials: 'MK', color: '#9065b0' }, due: '08.05.2026', progress: 100 },
3206
+ { id: 2, icon: '🗃️', name: 'Database table view', status: 'progress', owner: { name: 'Marcel', initials: 'MK', color: '#9065b0' }, due: '20.05.2026', progress: 65 },
3207
+ { id: 3, icon: '▤', name: 'Board view & drag-and-drop', status: 'planned', due: '02.06.2026', progress: 0 },
3208
+ ];
3209
+ const FILTERS = { done: { key: 'status', value: 'done', label: 'Status: Done' } };
3210
+ function render() {
3211
+ db.rows = filters.apply(rows);
3212
+ filterBtn.active = filters.filters.length > 0;
3213
+ filterMenu.querySelectorAll('nk-menu-item[type="check"]').forEach(i => { i.checked = filters.filters.includes(FILTERS[i.value]); });
3214
+ }
3215
+ db.columns = columns;
3216
+ render();
3217
+ filterBtn.addEventListener('click', () => filterMenu.toggle(filterBtn));
3218
+ filters.addEventListener('nk-action', e => filterMenu.show(e.detail.anchor)); // a pill or + Filter
3219
+ filterMenu.addEventListener('nk-change', e => {
3220
+ const f = FILTERS[e.detail.value];
3221
+ filters.filters = e.detail.checked ? [...filters.filters, f] : filters.filters.filter(x => x !== f);
3222
+ render();
3223
+ });
3224
+ filters.addEventListener('nk-change', render); // × on a pill
3225
+ newBtn.addEventListener('click', () => { rows.push({ id: Date.now(), icon: '📄', name: 'New page', status: 'planned', due: '—', progress: 0 }); render(); });
3226
+ let editing = null;
3227
+ db.addEventListener('nk-select', e => { // a row, a card, a list item, a calendar card
3228
+ if (e.detail.key === 'due') { // a due date in the table: the picker under its cell
3229
+ editing = e.detail.row;
3230
+ picker.value = editing.due.split('.').reverse().join('-');
3231
+ picker.show(e.detail.cell);
3232
+ return;
3233
+ }
3234
+ peekTitle.textContent = e.detail.row.name;
3235
+ peek.setAttribute('label', e.detail.row.name);
3236
+ peek.show(); // another row only swaps it
3237
+ });
3238
+ picker.addEventListener('nk-change', e => { editing.due = e.detail.value.split('-').reverse().join('.'); render(); });
3239
+ db.addEventListener('nk-change', e => toast.show(`${e.detail.row.name} → ${e.detail.value}`));
3240
+ db.addEventListener('nk-action', e => { if (e.detail.action === 'new-row') { rows.push({ id: Date.now(), icon: '📄', name: 'New page', status: e.detail.value || 'planned', due: '—', progress: 0 }); render(); } });
3241
+ </script>
3242
+ </body>
3243
+ </html>
3244
+ ```
3245
+
3246
+ Data contract: `columns` describe the properties (`type`: text | select | multi-select | date | person | checkbox | url | number | progress; a `select` carries `options: [{ value, label, color }]`; the title column has `title: true`), `rows` are plain objects keyed by `column.key` (a `person` is `{ name, initials, color }` or a string; `icon` on a row prefixes the title). The elements render what they get – filtering, sorting on the server, persistence are yours; `filters.apply(rows)` is the local filter, `op: 'is-not'` on a filter keeps the other rows. Assign a new array (`db.rows = …`) or call `db.refresh()` after mutating rows in place.
3247
+
3248
+ ## 4.4 AI chat page
3249
+
3250
+ **When:** assistant-centred apps where the conversation is the document.
3251
+
3252
+ ```html
3253
+ <!DOCTYPE html>
3254
+ <html lang="en" data-theme="light">
3255
+ <head>
3256
+ <meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
3257
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit@1.11.1/notionkit.min.css">
3258
+ <script src="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit-elements@1.11.1/dist/notionkit-elements.min.js"></script>
3259
+ </head>
3260
+ <body class="nk-body">
3261
+ <nk-app>
3262
+ <nk-sidebar slot="sidebar">
3263
+ <nk-workspace-switcher slot="workspace" name="MonaHilft"></nk-workspace-switcher>
3264
+ <nk-tree>
3265
+ <nk-tree-item icon="+" value="new" no-actions>New chat</nk-tree-item>
3266
+ <nk-section-label>Threads</nk-section-label>
3267
+ <nk-tree-item icon="✨" value="t1" active>Open tasks</nk-tree-item>
3268
+ <nk-tree-item icon="✨" value="t2">Release notes draft</nk-tree-item>
3269
+ </nk-tree>
3270
+ </nk-sidebar>
3271
+ <nk-topbar>
3272
+ <nk-breadcrumb><span>✨ Open tasks</span></nk-breadcrumb>
3273
+ <nk-theme-toggle slot="actions"></nk-theme-toggle>
3274
+ </nk-topbar>
3275
+ <nk-page icon="✨">
3276
+ <nk-page-title>Open tasks</nk-page-title>
3277
+ <nk-ai-thread id="thread">
3278
+ <nk-ai-msg role="user" name="You" avatar="MK">Summarise the open tasks for this project.</nk-ai-msg>
3279
+ <nk-ai-msg role="assistant" name="Mona" badge="· AI">Two tasks are open: the <b>table view</b> sits at 65 % (due 20 May), the <b>board with drag and drop</b> is planned.
3280
+ <button slot="actions" value="copy">📋 Copy</button>
3281
+ <button slot="actions" value="rephrase">↻ Rephrase</button>
3282
+ </nk-ai-msg>
3283
+ </nk-ai-thread>
3284
+ <nk-ai-input-row id="prompt" placeholder="Ask Mona something …"></nk-ai-input-row>
3285
+ </nk-page>
3286
+ </nk-app>
3287
+ <script>
3288
+ prompt.addEventListener('nk-submit', async e => {
3289
+ const user = document.createElement('nk-ai-msg');
3290
+ user.setAttribute('role', 'user'); user.setAttribute('name', 'You'); user.setAttribute('avatar', 'MK');
3291
+ user.textContent = e.detail.text;
3292
+ thread.appendChild(user);
3293
+ prompt.disabled = true;
3294
+ const reply = document.createElement('nk-ai-msg');
3295
+ reply.setAttribute('name', 'Mona'); reply.setAttribute('badge', '· AI');
3296
+ reply.textContent = await askYourBackend(e.detail.text); // your call
3297
+ thread.appendChild(reply);
3298
+ prompt.disabled = false;
3299
+ prompt.focus();
3300
+ });
3301
+ thread.addEventListener('nk-action', e => console.log(e.detail.action));
3302
+ async function askYourBackend(text) { return 'Echo: ' + text; }
3303
+ </script>
3304
+ </body>
3305
+ </html>
3306
+ ```
3307
+
3308
+ ## 4.3 Settings modal integration
3309
+
3310
+ **When:** you have an app already and need the settings overlay – plus the command palette and a toast, since they share the "overlay under body" rule (so do `<nk-sheet>` and a floating `<nk-menu>`).
3311
+
3312
+ ```html
3313
+ <!DOCTYPE html>
3314
+ <html lang="en" data-theme="light">
3315
+ <head>
3316
+ <meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
3317
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit@1.11.1/notionkit.min.css">
3318
+ <script src="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit-elements@1.11.1/dist/notionkit-elements.min.js"></script>
3319
+ </head>
3320
+ <body class="nk-body">
3321
+ <!-- your app -->
3322
+ <div class="nk-page" style="padding-top:48px">
3323
+ <nk-page-title>NotionKit MVP</nk-page-title>
3324
+ <p><nk-btn variant="secondary" onclick="settings.show()">⚙️ Open settings</nk-btn> <nk-btn variant="secondary" onclick="palette.show()">🔍 Open palette (⌘K)</nk-btn></p>
3325
+ </div>
3326
+
3327
+ <!-- overlays: direct children of <body> -->
3328
+ <nk-modal id="settings">
3329
+ <nk-settings-user slot="user" name="Marcel Karas" mail="marcel@monahilft.de"></nk-settings-user>
3330
+
3331
+ <nk-settings-pane name="profile" group="Account" icon="👤" label="My profile" title="My profile" active>
3332
+ <nk-image-picker id="photo" initials="MK" label="My profile"></nk-image-picker>
3333
+ <h3>Display name</h3>
3334
+ <nk-field label="Display name" desc="Shown next to your comments."><nk-input name="name" value="Marcel Karas"></nk-input></nk-field>
3335
+ <nk-field label="Email"><nk-input name="email" type="email" value="marcel@monahilft.de"></nk-input></nk-field>
3336
+ <p style="margin-top:16px"><nk-btn variant="primary" small onclick="toast.show('Settings saved')">Save</nk-btn></p>
3337
+ </nk-settings-pane>
3338
+
3339
+ <nk-settings-pane name="appearance" group="Account" icon="🎨" label="Appearance" title="Appearance">
3340
+ <nk-field label="Theme"><nk-select id="themeSelect"><option value="light">Light</option><option value="dark">Dark</option></nk-select></nk-field>
3341
+ <nk-field label="Font size"><nk-slider min="12" max="18" value="14" unit="px" show-value></nk-slider></nk-field>
3342
+ </nk-settings-pane>
3343
+
3344
+ <nk-settings-pane name="ai" group="Account" icon="✨" label="AI assistant" title="AI assistant">
3345
+ <nk-model-card name="model" value="pro" title="Mona Pro" desc="Best for long documents and research." selected></nk-model-card>
3346
+ <nk-model-card name="model" value="fast" title="Mona Fast" desc="Quick answers, lower cost."></nk-model-card>
3347
+ </nk-settings-pane>
3348
+
3349
+ <nk-settings-pane name="general" group="Workspace" icon="⚙️" label="General" title="General">
3350
+ <nk-field label="MonaHilft"><nk-input value="MonaHilft"></nk-input></nk-field>
3351
+ <nk-field label="Icon"><nk-image-picker square initials="A" max="256" type="image/png"></nk-image-picker></nk-field>
3352
+ <nk-field label="Public address"><nk-copy-field value="https://acme.example.com"></nk-copy-field></nk-field>
3353
+ <nk-danger-zone title="Danger zone"><nk-field label="Delete workspace" desc="Deleting the workspace removes every page."><nk-btn variant="danger-solid" small>Delete</nk-btn></nk-field></nk-danger-zone>
3354
+ </nk-settings-pane>
3355
+
3356
+ <nk-settings-pane name="members" group="Workspace" icon="👥" label="Members" title="Members">
3357
+ <nk-member-list>
3358
+ <nk-member-row name="Sara Lindt" mail="sara@example.com" color="#448361"><nk-select slot="role" compact value="editor"><option value="viewer">Viewer</option><option value="editor">Editor</option><option value="admin">Admin</option></nk-select></nk-member-row>
3359
+ <nk-member-row name="Tom Weber" mail="tom@example.com" color="#d9730d"><nk-select slot="role" compact value="viewer"><option value="viewer">Viewer</option><option value="editor">Editor</option><option value="admin">Admin</option></nk-select></nk-member-row>
3360
+ </nk-member-list>
3361
+ </nk-settings-pane>
3362
+ </nk-modal>
3363
+
3364
+ <nk-cmdk id="palette" placeholder="Search or type a command …"></nk-cmdk>
3365
+ <nk-toast id="toast"></nk-toast>
3366
+
3367
+ <script>
3368
+ palette.commands = [
3369
+ { group: 'Pages', items: [{ id: 'mvp', icon: '🚀', label: 'NotionKit MVP' }, { id: 'kb', icon: '🧠', label: 'Knowledge base' }] },
3370
+ { group: 'Actions', items: [
3371
+ { id: 'settings', icon: '⚙️', label: 'Open settings', shortcut: '⌘,', action: () => settings.show() },
3372
+ { id: 'theme', icon: '🌙', label: 'Toggle theme', shortcut: '⌘⇧L', action: () => document.documentElement.dataset.theme = document.documentElement.dataset.theme === 'dark' ? 'light' : 'dark' },
3373
+ ]},
3374
+ ];
3375
+ palette.addEventListener('nk-command', e => console.log('command', e.detail.id));
3376
+ themeSelect.addEventListener('nk-change', e => document.documentElement.dataset.theme = e.detail.value);
3377
+ settings.addEventListener('nk-select', e => console.log('pane', e.detail.value));
3378
+ photo.addEventListener('nk-change', e => console.log('upload', e.detail.size, 'bytes')); // a data URL, scaled, EXIF-rotated
3379
+ </script>
3380
+ </body>
3381
+ </html>
3382
+ ```
3383
+
3384
+ The open/close contract is one attribute: `settings.open = true`, `settings.show('members')`, `settings.close()`. Never add the class `open` yourself.
3385
+
3386
+ ## 4.5 Form / onboarding page
3387
+
3388
+ **When:** an app – or one step of it – made entirely of form elements. No sidebar, no editor.
3389
+
3390
+ ```html
3391
+ <!DOCTYPE html>
3392
+ <html lang="en" data-theme="light">
3393
+ <head>
3394
+ <meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
3395
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit@1.11.1/notionkit.min.css">
3396
+ <script src="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit-elements@1.11.1/dist/notionkit-elements.min.js"></script>
3397
+ </head>
3398
+ <body class="nk-body">
3399
+ <div class="nk-page" style="padding-top:48px">
3400
+ <h1 class="nk-page-title">Set up your workspace</h1>
3401
+ <p class="lead">Three short steps. Everything can be changed later in Settings.</p>
3402
+ <nk-steps id="progress" label="Set up your workspace" current="1" steps="Profile, Notifications, Assistant style"></nk-steps>
3403
+
3404
+ <form id="onboarding">
3405
+ <nk-heading>1 · Profile</nk-heading>
3406
+ <nk-field label="Display name" desc="Shown next to your comments."><nk-input name="name" required></nk-input></nk-field>
3407
+ <nk-field label="Email"><nk-input name="email" type="email" required></nk-input></nk-field>
3408
+ <nk-field label="Short bio" stacked><nk-textarea name="bio" rows="3" placeholder="A sentence about you"></nk-textarea></nk-field>
3409
+
3410
+ <nk-heading>2 · Notifications</nk-heading>
3411
+ <nk-field label="Email notifications"><nk-switch name="notify" checked></nk-switch></nk-field>
3412
+ <nk-check name="digest" value="weekly" checked>Weekly digest</nk-check>
3413
+ <nk-check name="digest" value="mentions">Mentions only</nk-check>
3414
+
3415
+ <nk-heading>3 · Assistant style</nk-heading>
3416
+ <nk-radio name="style" value="concise">Concise</nk-radio>
3417
+ <nk-radio name="style" value="balanced" checked>Balanced</nk-radio>
3418
+ <nk-radio name="style" value="detailed">Detailed</nk-radio>
3419
+ <nk-field label="Font size"><nk-slider name="size" min="12" max="18" value="14" unit="px" show-value></nk-slider></nk-field>
3420
+
3421
+ <nk-divider></nk-divider>
3422
+ <nk-callout icon="🔒">Nothing leaves your browser in this demo.</nk-callout>
3423
+ <p style="margin-top:16px"><nk-btn type="submit" variant="primary">Finish</nk-btn> <nk-btn type="reset" variant="secondary">Reset</nk-btn></p>
3424
+ </form>
3425
+ </div>
3426
+ <script>
3427
+ onboarding.addEventListener('submit', e => { e.preventDefault(); console.log(Object.fromEntries(new FormData(onboarding))); });
3428
+ </script>
3429
+ </body>
3430
+ </html>
3431
+ ```
3432
+
3433
+ ## 4.6 Landing / documentation page
3434
+
3435
+ **When:** a public page in the NotionKit look – no sidebar, the page *is* the document. For a complete website use [NotionKit Web](https://notionkit-web.jungherz.com), the Astro template on the same foundation.
3436
+
3437
+ ```html
3438
+ <!DOCTYPE html>
3439
+ <html lang="en" data-theme="light">
3440
+ <head>
3441
+ <meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
3442
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit@1.11.1/notionkit.min.css">
3443
+ <script src="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit-elements@1.11.1/dist/notionkit-elements.min.js"></script>
3444
+ </head>
3445
+ <body class="nk-body">
3446
+ <nk-page narrow icon="📘" cover>
3447
+ <nk-page-title>NotionKit Elements</nk-page-title>
3448
+ <nk-page-actions><span>👤 Marcel Karas</span><span>📅 Created 12 May 2026</span><span>🏷️ <nk-tag color="green">Done</nk-tag></span></nk-page-actions>
3449
+ <p class="lead">A calm, document-centric workspace app – built from elements only.</p>
3450
+
3451
+ <nk-banner variant="info">ℹ️ <span>This page is a <b>component preview</b> – every element follows the same design tokens.</span><button slot="action" type="button">View</button></nk-banner>
3452
+ <nk-callout icon="💡"><b>Core idea:</b> A callout carries one thought that must not be missed.</nk-callout>
3453
+
3454
+ <nk-heading>Getting started</nk-heading>
3455
+ <nk-code lang="html" highlight>&lt;nk-btn variant="primary"&gt;Save&lt;/nk-btn&gt;</nk-code>
3456
+
3457
+ <nk-heading>Building blocks</nk-heading>
3458
+ <nk-tabs value="notes">
3459
+ <nk-tab value="notes">📝 Notes</nk-tab><nk-tab value="tasks">✅ Tasks</nk-tab>
3460
+ <div slot="panel" data-tab="notes" class="nk-tab-panel">Free-form notes on the project – meeting minutes, ideas, rough drafts.</div>
3461
+ <div slot="panel" data-tab="tasks" class="nk-tab-panel">Tasks for this project, linked to the database below.</div>
3462
+ </nk-tabs>
3463
+ <nk-stats>
3464
+ <nk-stat label="Active pages" value="128" delta="▲ 12 this week" trend="up"></nk-stat>
3465
+ <nk-stat label="Open tasks" value="14" delta="▼ 5 since yesterday" trend="down"></nk-stat>
3466
+ </nk-stats>
3467
+
3468
+ <nk-toggle label="Details" open>Folded content lives here.</nk-toggle>
3469
+ <nk-quote cite="Unknown">The best interface is the one that gets out of the way.</nk-quote>
3470
+ <nk-divider></nk-divider>
3471
+ <nk-empty icon="🗂️" title="No entries yet" desc="Create the first entry or import existing data."><nk-btn variant="primary" small>+ New entry</nk-btn></nk-empty>
3472
+ </nk-page>
3473
+ </body>
3474
+ </html>
3475
+ ```
3476
+
3477
+ Note `narrow`: the page is the document, so there is no inner scroll wrapper – the browser scrolls. Inside `<nk-app>` leave it off.
3478
+ ## 4.7 Home page
3479
+
3480
+ **When:** the first screen after sign-in – a greeting, the pages someone comes back to, what is due next. Notion calls it Home; LearnHub builds it as “My courses”, Auxdesk as “Overview”. Full width and small text, panels with a picture cover, a list for what is next.
3481
+
3482
+ ```html
3483
+ <!DOCTYPE html>
3484
+ <html lang="en" data-theme="light">
3485
+ <head>
3486
+ <meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
3487
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit@1.11.1/notionkit.min.css">
3488
+ <script src="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit-elements@1.11.1/dist/notionkit-elements.min.js"></script>
3489
+ </head>
3490
+ <body class="nk-body">
3491
+ <nk-app>
3492
+ <nk-sidebar slot="sidebar">
3493
+ <nk-workspace-switcher slot="workspace" name="MonaHilft"></nk-workspace-switcher>
3494
+ <nk-tree>
3495
+ <nk-tree-item icon="🏠" value="home" active>Home</nk-tree-item>
3496
+ <nk-tree-item icon="🚀" value="mvp">NotionKit MVP</nk-tree-item>
3497
+ </nk-tree>
3498
+ </nk-sidebar>
3499
+ <nk-topbar><nk-breadcrumb><span>🏠 Home</span></nk-breadcrumb><nk-theme-toggle slot="actions"></nk-theme-toggle></nk-topbar>
3500
+
3501
+ <!-- An app view, not a document: the whole width, 14px text. -->
3502
+ <nk-page full small>
3503
+ <nk-page-title>Good morning, Ada</nk-page-title>
3504
+ <nk-heading>🕘 Recently visited</nk-heading>
3505
+ <nk-panels>
3506
+ <nk-panel href="/roadmap" cover="covers/roadmap.jpg" icon="🚀" title="Roadmap"><p><nk-avatar size="small">AL</nk-avatar> 2 min ago</p></nk-panel>
3507
+ <nk-panel href="/kb" cover icon="📚" title="Knowledge base"><p>Yesterday</p></nk-panel>
3508
+ <nk-panel href="/onboarding" icon="🧭" title="Onboarding"><p>Monday</p></nk-panel>
3509
+ </nk-panels>
3510
+ <nk-heading>📌 Upcoming</nk-heading>
3511
+ <nk-list-view id="upcoming" meta-keys="due,status"></nk-list-view>
3512
+ <nk-heading>📊 This week</nk-heading>
3513
+ <nk-panels>
3514
+ <nk-panel title="Weekly review"><p>Three pages changed, one comment is waiting for an answer.</p><nk-progress value="60" label="60 %" wide></nk-progress></nk-panel>
3515
+ <nk-panel title="Release notes"><div class="nk-prose"><p>Version 2.4 ships the list view. <a href="/changelog">Read more</a></p></div></nk-panel>
3516
+ </nk-panels>
3517
+ </nk-page>
3518
+ </nk-app>
3519
+ <script>
3520
+ upcoming.columns = [
3521
+ { key: 'name', label: 'Name', title: true }, { key: 'due', label: 'Due', type: 'date' },
3522
+ { key: 'status', label: 'Status', type: 'select', options: [{ value: 'progress', label: 'In progress', color: 'blue' }, { value: 'planned', label: 'Planned', color: 'orange' }] },
3523
+ ];
3524
+ upcoming.rows = [{ id: 1, icon: '🗃️', name: 'Table view', due: '20 May', status: 'progress' }, { id: 2, icon: '▤', name: 'Board with drag and drop', due: '2 June', status: 'planned' }];
3525
+ upcoming.addEventListener('nk-select', e => location.assign('/tasks/' + e.detail.id));
3526
+ </script>
3527
+ </body>
3528
+ </html>
3529
+ ```
3530
+
3531
+ `nk-panels` falls to one column on a phone; the list keeps one line per row and cuts the title first. A slotted `<p>` in a panel is styled by the panel – inside it, `<nk-avatar>` brings its own size.
3532
+
3533
+ ## 4.8 Sign-in page
3534
+
3535
+ **When:** the page before the app – sign in with a provider or by email. A narrow centred column in a panel, no sidebar. Not an element on purpose: panel, field and buttons already are one.
3536
+
3537
+ ```html
3538
+ <!DOCTYPE html>
3539
+ <html lang="en" data-theme="light">
3540
+ <head>
3541
+ <meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
3542
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit@1.11.1/notionkit.min.css">
3543
+ <script src="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit-elements@1.11.1/dist/notionkit-elements.min.js"></script>
3544
+ </head>
3545
+ <body class="nk-body">
3546
+ <!-- A narrow column, centred: layout is yours, so it is inline. -->
3547
+ <nk-page narrow>
3548
+ <div style="max-width:420px;margin:12vh auto 0;display:flex;flex-direction:column;gap:14px">
3549
+ <div style="text-align:center">
3550
+ <nk-avatar size="xlarge" square>A</nk-avatar>
3551
+ <nk-page-title>Sign in to Acme</nk-page-title>
3552
+ <div style="color:var(--nk-text-secondary)">Use your work account.</div>
3553
+ </div>
3554
+ <form id="signin">
3555
+ <nk-panel>
3556
+ <nk-btn variant="secondary" type="button">Continue with Google</nk-btn>
3557
+ <nk-btn variant="secondary" type="button">Continue with Microsoft</nk-btn>
3558
+ <nk-field label="Email" stacked><nk-input name="email" type="email" placeholder="ada@acme.com" required></nk-input></nk-field>
3559
+ <nk-btn variant="primary" type="submit">Continue with email</nk-btn>
3560
+ </nk-panel>
3561
+ </form>
3562
+ <nk-banner variant="success" id="sent" hidden>✉️ Check your inbox – we sent you a sign-in link.</nk-banner>
3563
+ </div>
3564
+ </nk-page>
3565
+ <script>
3566
+ signin.addEventListener('submit', e => { e.preventDefault(); sent.hidden = false; });
3567
+ </script>
3568
+ </body>
3569
+ </html>
3570
+ ```
3571
+
3572
+ The form takes part in `FormData` through `nk-input`; `hidden` on the banner works on every element.
3573
+
3574
+
3575
+ # 5. State & Event Overview
3576
+
3577
+ | Event | Fired by | `detail` |
3578
+ |---|---|---|
3579
+ | `nk-change` | every form control, `nk-segmented`, `nk-tabs`, `nk-tab-bar`, editable `nk-page-title` | `{ value, name }` – checkables add `checked` |
3580
+ | `nk-input` | `nk-input`, `nk-textarea`, `nk-slider` | `{ value, name }` on every keystroke / drag |
3581
+ | `nk-toggle` | `nk-toggle`, tree branches, overlays | `{ open }` |
3582
+ | `nk-select` | tree items, menu items, breadcrumb, tabs, tab bar items, palette rows | `{ value, label, … }` |
3583
+ | `nk-view-change` | `nk-database` | `{ view }` |
3584
+ | `nk-command` | `nk-cmdk` | `{ id, item, query }` |
3585
+ | `nk-submit` | comment and AI input rows | `{ text }` |
3586
+ | `nk-action` | hover actions (tree +/⋯, section +, new row …) | `{ action, value? }` |
3587
+
3588
+ Form controls additionally re-dispatch a native, bubbling `change` event, so `form.addEventListener('change', …)` keeps working.
3589
+
3590
+
3591
+ # 6. Rules & Common Mistakes
3592
+
3593
+ ### Always follow
3594
+
3595
+ 1. `notionkit.css` on the document and `class="nk-body"` on `<body>` – the shadow roots inherit from there.
3596
+ 2. `data-theme` on `<html>` only – the observer watches nothing else.
3597
+ 3. Form controls inside a `<form>` if their value should be submitted; `FormData` reads them like native fields.
3598
+ 4. Pass icons as the slotted node itself: `<span slot="icon">📌</span>`.
3599
+ 5. Toggle state through attributes or properties (`el.open = true`, `el.setAttribute('active', '')`), never through classes inside the shadow root.
3600
+ 6. `<nk-select>` options are direct `<option>`/`<optgroup>` children; change them in the light DOM and the element follows.
3601
+ 7. Brand on `:root`, not on a subtree – tokens are inherited into every shadow root from the document.
3602
+ 8. Import the bundle once per page. `customElements.define` throws on a second definition.
3603
+
3604
+ ### Common mistakes
3605
+
3606
+ | Mistake | Correction |
3607
+ |---|---|
3608
+ | `<span slot="icon"><svg/></span>` (wrapped icon) | `<svg slot="icon">` – `::slotted()` matches only the assigned node |
3609
+ | Declaring `--nk-*` tokens inside a shadow root, or adopting the full `nkSheet` | Tokens go on the document (`:root`); elements adopt `componentsSheet` only |
3610
+ | Injecting your own CSS into `element.shadowRoot` | Restyle through tokens on `:root`; the elements carry no CSS of their own |
3611
+ | `data-theme` on a `<nk-*>` element or a wrapper div | Only `<html data-theme>` is observed |
3612
+ | `el.shadowRoot.querySelector('.nk-btn').classList.add('primary')` | `el.variant = 'primary'` |
3613
+ | `<nk-radio>`s with different `name`s expected to exclude each other | Same `name` in the same tree and form makes the group |
3614
+ | A form control outside `<form>` expected in `FormData` | Put it inside the form (or read `el.value`) |
3615
+ | `<button class="nk-btn">` inside `<nk-btn>` | The element renders the button – slot only the label and icon |
3616
+ | Loading the bundle without `notionkit.css` and wondering about the serif font | The token layer only covers colours and metrics; typography comes from `.nk-body` |
3617
+ | `<nk-btn style="margin-top:16px">` or `nk-callout { margin: … }` | Hosts are `display: contents` and have no box – put spacing on a wrapper you own |
3618
+ | A positioned wrapper around `<nk-menu>` to open it under a button | `<nk-menu floating sheet>` and `menu.show(button)`: it measures the button, closes on a tap outside and is a sheet on a phone |
3619
+ | `<nk-sheet id="more">` opened from `app.html#more` | Give the overlay an id other than the hash: the browser scrolls to the fragment target and takes the focus the overlay just gave |
3620
+ | `<nk-btn variant="topbar" onclick="sidebar.toggle()">☰</nk-btn>` plus a script that hides it on the desktop | `<nk-btn variant="sidebar">☰</nk-btn>` – shown below 860px only, opens the drawer |
3621
+ | A `<pre>` with a Copy button that writes `navigator.clipboard` itself | `<nk-copy-field value="…" mono>` – Copy, the green moment after, the ⌘C fallback; `secret` for keys |
3622
+ | A help or detail panel as a positioned `<aside>` with its own close logic | `<nk-peek>` – beside the page on the desktop, a sheet on a phone, Escape and outside clicks included |
3623
+ | A file input plus canvas code for an avatar or logo | `<nk-image-picker>` – EXIF rotation, scaling, a data URL in `nk-change` |
3624
+ | A positioned `<div>` with a scrim and its own Escape and focus logic for “Are you sure?” | `<nk-dialog alert title="…">` with `<nk-btn slot="actions" value="…">` – `nk-close` says which, and can be cancelled to check an input |
3625
+ | `title="…"` on buttons for hints, or a hand-positioned hint | One `<nk-tooltip>` and `data-tooltip` (and `data-tooltip-key`) on the buttons; `tip.show(rect, text)` for chart bars |
3626
+ | `hidden` on the sidebar plus an own button to widen the page on the desktop | `<nk-sidebar collapsible>` – the «, the ☰ of `<nk-btn variant="sidebar">`, ⌘\; `nk-collapse` to keep the state |
3627
+ | A padding on the page while the side peek is open, or a peek wider by CSS | `<nk-peek resizable inset>` – the width is `--nk-peek-width`, `nk-resize` to keep it |
3628
+ | A hand-built month grid, or `<input type="date">` for a date property | `<nk-calendar floating sheet>` and `picker.show(cell)` – the table's `nk-select` names the `key` and the `cell`; `range` for start and end, `weeks` for calendar weeks, `days` for holidays and marks |
3629
+ | Rows laid out in a table of weeks to show them by date | `<nk-calendar-view date-key="due">` in `<nk-database>` – a tab like table and board |
3630
+
3631
+
3632
+ # 7. Quick Reference
3633
+
3634
+ | Tag | Group | Key attributes | Key slots | Key events |
3635
+ |---|---|---|---|---|
3636
+ | `<nk-btn>` | forms | `variant`, `active`, `small`, `disabled` | `(default)` | `click` |
3637
+ | `<nk-input>` | forms | `value`, `type`, `placeholder`, `name` | – | `nk-change`, `nk-input` |
3638
+ | `<nk-copy-field>` | forms | `value`, `secret`, `mono`, `wrap` | – | `nk-action` |
3639
+ | `<nk-image-picker>` | forms | `src`, `initials`, `square`, `max` | – | `nk-change`, `nk-error` |
3640
+ | `<nk-calendar>` | forms | `value`, `month`, `min`, `max` | – | `nk-change`, `nk-month`, `nk-toggle` |
3641
+ | `<nk-textarea>` | forms | `value`, `placeholder`, `rows`, `name` | `(default)` | `nk-change`, `nk-input` |
3642
+ | `<nk-select>` | forms | `value`, `name`, `disabled`, `required` | `(default)` | `nk-change` |
3643
+ | `<nk-switch>` | forms | `checked`, `name`, `disabled`, `value` | `(default)` | `nk-change` |
3644
+ | `<nk-check>` | forms | `checked`, `indeterminate`, `name`, `disabled` | `(default)` | `nk-change` |
3645
+ | `<nk-radio>` | forms | `checked`, `name`, `disabled`, `value` | `(default)` | `nk-change` |
3646
+ | `<nk-slider>` | forms | `value`, `min`, `max`, `step` | – | `nk-change`, `nk-input` |
3647
+ | `<nk-field>` | forms | `label`, `desc`, `stacked`, `compact` | `(default)`, `label`, `desc` | – |
3648
+ | `<nk-fields>` | forms | `fit` | `(default)` | – |
3649
+ | `<nk-tag>` | content | `color` | `(default)` | – |
3650
+ | `<nk-progress>` | content | `value`, `max`, `label`, `wide` | – | – |
3651
+ | `<nk-callout>` | content | `icon` | `(default)`, `icon` | – |
3652
+ | `<nk-bookmark>` | content | `href`, `title`, `desc`, `favicon` | – | `click` |
3653
+ | `<nk-divider>` | content | – | – | – |
3654
+ | `<nk-heading>` | content | `level` | `(default)` | – |
3655
+ | `<nk-toggle>` | content | `label`, `open` | `(default)`, `label` | `nk-toggle` |
3656
+ | `<nk-todo>` | content | `checked`, `name`, `disabled`, `value` | `(default)` | `nk-change` |
3657
+ | `<nk-kbd>` | content | – | `(default)` | – |
3658
+ | `<nk-code>` | content | `lang`, `highlight` | `(default)` | – |
3659
+ | `<nk-quote>` | content | `cite` | `(default)` | – |
3660
+ | `<nk-app>` | shell | `peek-inset` | `sidebar`, `(default)` | – |
3661
+ | `<nk-sidebar>` | shell | `open`, `collapsible`, `collapsed`, `collapse-label` | `workspace`, `(default)`, `footer` | `nk-toggle`, `nk-collapse` |
3662
+ | `<nk-workspace-switcher>` | shell | `name`, `avatar`, `open` | `avatar`, `menu` | `nk-toggle`, `nk-select` |
3663
+ | `<nk-section-label>` | shell | `addable`, `label` | `(default)` | `nk-action` |
3664
+ | `<nk-tree>` | shell | `manual` | `(default)` | `nk-select`, `nk-toggle`, `nk-action` |
3665
+ | `<nk-tree-item>` | shell | `icon`, `label`, `value`, `href` | `(default)`, `icon`, `end` | `nk-select`, `nk-toggle`, `nk-action` |
3666
+ | `<nk-topbar>` | shell | – | `(default)`, `actions` | – |
3667
+ | `<nk-breadcrumb>` | shell | `separator` | `(default)` | `nk-select` |
3668
+ | `<nk-theme-toggle>` | shell | `storage-key`, `title` | – | `nk-change` |
3669
+ | `<nk-tab-bar>` | shell | `value`, `always`, `fixed`, `floating` | `(default)` | `nk-change`, `nk-select` |
3670
+ | `<nk-tab-bar-item>` | shell | `icon`, `value`, `label`, `href` | `(default)`, `icon` | `nk-select` |
3671
+ | `<nk-page>` | page | `icon`, `full`, `small`, `cover` | `(default)`, `cover`, `icon` | `nk-action` |
3672
+ | `<nk-page-cover>` | page | `src`, `position` | – | – |
3673
+ | `<nk-page-title>` | page | `editable`, `placeholder`, `value` | `(default)` | `nk-change` |
3674
+ | `<nk-page-actions>` | page | – | `(default)` | – |
3675
+ | `<nk-props>` | page | – | `(default)` | – |
3676
+ | `<nk-prop>` | page | `label`, `icon` | `(default)`, `icon` | `nk-action` |
3677
+ | `<nk-panels>` | page | – | `(default)` | – |
3678
+ | `<nk-panel>` | page | `title`, `icon`, `cover`, `href` | `(default)` | – |
3679
+ | `<nk-block-host>` | page | `handle`, `drop-target` | `(default)` | – |
3680
+ | `<nk-banner>` | page | `variant` | `(default)`, `action` | – |
3681
+ | `<nk-empty>` | page | `icon`, `title`, `desc` | `(default)`, `icon`, `title`, `desc` | – |
3682
+ | `<nk-skeleton>` | page | `lines`, `height`, `width`, `widths` | – | – |
3683
+ | `<nk-synced>` | page | `badge` | `(default)` | – |
3684
+ | `<nk-tabs>` | page | `value` | `(default)`, `panel` | `nk-change`, `nk-select` |
3685
+ | `<nk-tab>` | page | `value`, `active`, `disabled` | `(default)` | `nk-select` |
3686
+ | `<nk-segmented>` | page | `value`, `scroll`, `wrap`, `name` | `(default)` | `nk-change` |
3687
+ | `<nk-steps>` | page | `steps`, `current`, `label` | – | – |
3688
+ | `<nk-stats>` | page | `label`, `value`, `delta`, `trend` | `(default)` | – |
3689
+ | `<nk-stat>` | page | `label`, `value`, `delta`, `trend` | `label`, `value`, `delta` | – |
3690
+ | `<nk-avatar-group>` | page | `more` | `(default)` | – |
3691
+ | `<nk-avatar>` | page | `size`, `color`, `square`, `name` | `(default)` | – |
3692
+ | `<nk-mention>` | page | `type` | `avatar`, `(default)` | – |
3693
+ | `<nk-template-btn>` | page | `icon`, `value`, `disabled` | `(default)` | `nk-select` |
3694
+ | `<nk-model-card>` | page | `title`, `desc`, `name`, `disabled` | `title`, `desc` | `nk-change`, `nk-select` |
3695
+ | `<nk-profile-row>` | page | `avatar` | `avatar`, `(default)` | – |
3696
+ | `<nk-danger-zone>` | page | `title` | `(default)` | – |
3697
+ | `<nk-member-list>` | page | `name`, `mail`, `avatar`, `color` | `(default)`, `role`, `avatar` | – |
3698
+ | `<nk-member-row>` | page | `name`, `mail`, `avatar`, `color` | `role`, `avatar`, `(default)` | – |
3699
+ | `<nk-modal>` | overlays | `open`, `pane` | `(default)`, `user`, `nav` | `nk-toggle`, `nk-select` |
3700
+ | `<nk-sheet>` | overlays | `open`, `title` | `(default)` | `nk-toggle` |
3701
+ | `<nk-peek>` | overlays | `open`, `label`, `close-label`, `resizable` | `(default)`, `actions` | `nk-toggle`, `nk-resize` |
3702
+ | `<nk-dialog>` | overlays | `open`, `title`, `alert`, `wide` | `(default)`, `actions` | `nk-close`, `nk-toggle` |
3703
+ | `<nk-settings-pane>` | overlays | `name`, `label`, `icon`, `group` | `(default)` | – |
3704
+ | `<nk-settings-user>` | overlays | `name`, `mail`, `avatar` | `avatar` | – |
3705
+ | `<nk-cmdk>` | overlays | `open`, `hotkey`, `placeholder` | `footer` | `nk-command`, `nk-toggle` |
3706
+ | `<nk-menu>` | overlays | `floating`, `sheet`, `open`, `align` | `(default)` | `nk-select`, `nk-toggle` |
3707
+ | `<nk-menu-item>` | overlays | `icon`, `shortcut`, `value`, `danger` | `(default)`, `icon` | `nk-select`, `nk-change` |
3708
+ | `<nk-pop>` | overlays | `open`, `placement`, `bare` | `trigger`, `(default)` | `nk-toggle` |
3709
+ | `<nk-emoji-picker>` | overlays | `placeholder`, `value` | – | `nk-select` |
3710
+ | `<nk-toast>` | overlays | `open`, `duration`, `icon` | `(default)` | `nk-toggle` |
3711
+ | `<nk-tooltip>` | overlays | `for`, `shortcut`, `delay`, `placement` | `(default)` | – |
3712
+ | `<nk-database>` | data | `view`, `add-view` | `(default)`, `tools`, `filters` | `nk-view-change`, `nk-action`, `nk-select / nk-change / nk-action` |
3713
+ | `<nk-table-view>` | data | `name`, `label`, `badge`, `count` | – | `nk-select`, `nk-change`, `nk-action` |
3714
+ | `<nk-board-view>` | data | `name`, `label`, `group-by`, `title-key` | – | `nk-select`, `nk-change`, `nk-action` |
3715
+ | `<nk-list-view>` | data | `name`, `label`, `title-key`, `meta-keys` | – | `nk-select`, `nk-action` |
3716
+ | `<nk-calendar-view>` | data | `name`, `label`, `date-key`, `title-key` | – | `nk-select`, `nk-month` |
3717
+ | `<nk-filter-bar>` | data | `add`, `add-label`, `no-filter`, `no-sort` | `(default)` | `nk-change`, `nk-action` |
3718
+ | `<nk-comments>` | data | `placeholder`, `send-label`, `no-input`, `disabled` | `(default)` | `nk-submit` |
3719
+ | `<nk-comment>` | data | `author`, `time`, `avatar`, `color` | `(default)`, `head`, `avatar` | – |
3720
+ | `<nk-ai-thread>` | data | – | `(default)` | `nk-action` |
3721
+ | `<nk-ai-msg>` | data | `role`, `name`, `badge`, `avatar` | `(default)`, `actions`, `avatar` | `nk-action` |
3722
+ | `<nk-ai-input-row>` | data | `placeholder`, `value`, `disabled`, `icon` | – | `nk-submit` |
3723
+
3724
+ # 8. Framework Integration
3725
+
3726
+ - **Vanilla:** attributes for static config, properties for data, `addEventListener('nk-change', …)`.
3727
+ - **React:** use `ref` for properties and events (`ref.current.addEventListener('nk-change', …)`); boolean attributes need `checked={true ? '' : undefined}` or property assignment. React 19 sets properties automatically.
3728
+ - **Vue 3:** `app.config.compilerOptions.isCustomElement = tag => tag.startsWith('nk-')`; bind data with `.prop` (`:rows.prop="rows"`), listen with `@nk-change`.
3729
+ - **Svelte:** works out of the box; `on:nk-change`; properties via `bind:this` + assignment.
3730
+ - **SSR:** the elements render client-side. Server-render the page with `.nk-*` class markup where first paint matters and let the elements take over the interactive parts.
3731
+
3732
+
3733
+ # 9. Architecture Notes
3734
+
3735
+ | Concept | Location |
3736
+ |---|---|
3737
+ | Base classes `NkElement` / `NkFormElement` | `src/base.js` – shadow root, adopted `componentsSheet`, theme wrapper, `render/setupEvents/teardownEvents/onAttributeChanged/projectLightDom/refresh`, ElementInternals |
3738
+ | Token injection | `src/base.js` – once per page, `@layer notionkit-defaults { tokensCss }` appended to `document.adoptedStyleSheets` |
3739
+ | Theme sync | one `MutationObserver` on `<html>[data-theme]`, a `Set` of instances, `.nk-wrapper[data-theme]` inside each root |
3740
+ | Components | `src/components/{forms,content,shell,page,overlays,data}/nk-*.js`, one tag per file, `customElements.define` at the bottom |
3741
+ | Build | Rollup: IIFE, minified IIFE, ESM, and per-component ESM entries on a stable `dist/components/base.js` that import NotionKit's sheet (`@jungherz-de/notionkit/notionkit-styles.js`) instead of inlining it; the full bundles inline it and export `componentsSheet` |
3742
+ | Peer | `@jungherz-de/notionkit >= 1.11.1` – from 1.5.0 on the elements and the foundation share one version number; the bundle embeds that release's stylesheet, so keep them in step |
3743
+
3744
+ Lifecycle: construct (attach shadow, adopt sheets) → first connect (wrapper + `render()`) → every connect (`setupEvents()`, theme registration, light-DOM observer) → `attributeChangedCallback` → `onAttributeChanged` → disconnect (`teardownEvents()`, unregister).
3745
+
3746
+
3747
+ ---
3748
+ *NotionKit Elements v1.11.1 · wrapping NotionKit CSS v1.11.1 · MIT · Jungherz GmbH*