Documentation

Every component with a live preview, the markup to copy, the classes involved and what it does on a small screen. Nothing here is generated from a screenshot — the previews are the real thing.

Installation

One stylesheet. Pick whichever of these fits your project.

html<!-- CDN --> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit@1/notionkit.min.css"> <!-- local --> <link rel="stylesheet" href="notionkit.css"> <!-- your own theme, after the library --> <link rel="stylesheet" href="theme-override.css">
htmlnpm install @jungherz-de/notionkit

The theme lives on <html>. Light is the default, so the attribute is only needed for dark:

html<html lang="en" data-theme="dark"> <body class="nk-body"> … </body> </html>

Add class="nk-body" to your <body> to pick up the font, background and text colour. It is opt-in: without it, NotionKit touches nothing outside its own classes.

Design tokens

Every visual value is a custom property. The two blocks below are the entire token surface — :root for light, [data-theme="dark"] for the overrides.

TokenLightDarkWhat it is
--nk-bg#ffffff#191919Page background
--nk-bg-sidebar#f9f8f7#202020Sidebar and settings nav
--nk-bg-hoverrgba(55,53,47,0.06)rgba(255,255,255,0.055)Hover wash — a darkening, never a hue change
--nk-bg-activergba(55,53,47,0.08)rgba(255,255,255,0.08)Active/selected row
--nk-bg-callout#f0efed#252525Callouts, badges, key caps, segmented track
--nk-bg-code#f7f6f3#202020Code block background
--nk-bg-card#ffffff#202020Board cards and white buttons — separate from --nk-bg so dark mode can lift them
--nk-bg-inputrgba(242,241,238,0.6)rgba(255,255,255,0.055)The filled input surface: inputs, selects, textareas, the emoji search
--nk-text#2c2c2brgba(255,255,255,0.81)Body text. Never pure black
--nk-text-secondary#7d7a75rgba(255,255,255,0.46)Secondary text, table headers, descriptions
--nk-text-tertiary#91918ergba(255,255,255,0.32)Meta, placeholders, section labels
--nk-text-sidebar#5f5e5brgba(255,255,255,0.62)Tree items, breadcrumbs, topbar buttons — one step darker than secondary, as in Notion
--nk-borderrgba(28,19,1,0.11)rgba(255,255,255,0.094)Hairline separators, table grid, input borders
--nk-border-strongrgba(27,21,0,0.19)rgba(255,255,255,0.16)Input hover, scrollbar thumbs, dashed frames, checkboxes
--nk-accent#2383e2#529CCAThe one brand colour. Everything accented is mixed from it
--nk-on-accent#ffffff—Text and marks on top of the accent or danger fill
--nk-danger#cd3c3a#df5452Destructive actions, inline code, negative deltas
--nk-tag-gray-bg#e3e2e0#373737Tag fill (gray)
--nk-tag-gray-text#32302crgba(255,255,255,0.85)Tag text (gray), near-black on the fill
--nk-tag-brown-bg#eee0da#603b2cTag fill (brown)
--nk-tag-brown-text#442a1ergba(255,255,255,0.85)Tag text (brown), near-black on the fill
--nk-tag-orange-bg#fadec9#854c1dTag fill (orange)
--nk-tag-orange-text#49290ergba(255,255,255,0.85)Tag text (orange), near-black on the fill
--nk-tag-yellow-bg#fdecc8#89632aTag fill (yellow)
--nk-tag-yellow-text#402c1brgba(255,255,255,0.85)Tag text (yellow), near-black on the fill
--nk-tag-green-bg#dbeddb#2b593fTag fill (green)
--nk-tag-green-text#1c3829rgba(255,255,255,0.85)Tag text (green), near-black on the fill
--nk-tag-blue-bg#d3e5ef#28456cTag fill (blue)
--nk-tag-blue-text#183347rgba(255,255,255,0.85)Tag text (blue), near-black on the fill
--nk-tag-purple-bg#e8deee#492f64Tag fill (purple)
--nk-tag-purple-text#412454rgba(255,255,255,0.85)Tag text (purple), near-black on the fill
--nk-tag-pink-bg#f5e0e9#69314cTag fill (pink)
--nk-tag-pink-text#4c2337rgba(255,255,255,0.85)Tag text (pink), near-black on the fill
--nk-tag-red-bg#ffe2dd#6e3630Tag fill (red)
--nk-tag-red-text#5d1715rgba(255,255,255,0.85)Tag text (red), near-black on the fill
--nk-tint-gray#f1f1ef#252525Soft gray surface for banners and tinted blocks
--nk-color-gray#787774#9b9b9bColoured text and marks (gray)
--nk-tint-brown#f4eeee#2e2724Soft brown surface for banners and tinted blocks
--nk-color-brown#9f6b53#ba856fColoured text and marks (brown)
--nk-tint-orange#fbecdd#36291fSoft orange surface for banners and tinted blocks
--nk-color-orange#d9730d#c77d48Coloured text and marks (orange) — also date mentions and code attributes
--nk-tint-yellow#fbf3db#372e20Soft yellow surface for banners and tinted blocks
--nk-color-yellow#cb912f#ca8f3dColoured text and marks (yellow)
--nk-tint-green#edf3ec#242b26Soft green surface for banners and tinted blocks
--nk-color-green#448361#529e72Coloured text and marks (green) — also the positive delta
--nk-tint-blue#e7f3f8#1f282dSoft blue surface for banners and tinted blocks — also the progress track
--nk-color-blue#337ea9#5e87c9Coloured text and marks (blue) — also the progress fill
--nk-tint-purple#f8f3fc#2a2430Soft purple surface for banners and tinted blocks
--nk-color-purple#9065b0#9d68d3Coloured text and marks (purple)
--nk-tint-pink#fcf1f6#2e2328Soft pink surface for banners and tinted blocks
--nk-color-pink#c14c8a#d15796Coloured text and marks (pink)
--nk-tint-red#fdebec#332523Soft red surface for banners and tinted blocks
--nk-color-red#cd3c3a#df5452Coloured text and marks (red)
--nk-decor-purple#9065b0—Avatar gradient start and cover. Same in both themes on purpose
--nk-decor-blue#529cca—Avatar gradient end and cover
--nk-decor-orange#d9730d—Cover accent
--nk-scrimrgba(15,15,15,0.6)—Modal backdrop
--nk-scrim-softrgba(15,15,15,0.5)—Command palette backdrop, one step lighter
--nk-sidebar-width260px—Sidebar width, also its min-width (plus the left safe-area inset, if any)
--nk-peek-width560px—Width of the side peek; .pk-resize sets it on :root while dragging
--nk-page-full-maxnone—Cap of a .nk-page.full column – none: the whole window, 1080px for a dashboard without 1600px lines
--nk-cover-height200px—Height of the page cover band – on :root or on one cover; Notion takes about 30vh
--nk-panel-cover-height64px—Height of a cover inside a panel, the band of a page tile
--nk-tab-bar-height58px—Mobile tab bar height without the safe-area inset (the inset replaces the 6px bottom padding); the spacer uses the same value
--nk-radius6px—Control radius. Cards and modals use 8–12px directly
--nk-fontui-sans-serif, -apple-system, "Segoe UI", Inter, Helvetica, Arial, sans-serif—System font stack
--nk-font-monoui-monospace, "SF Mono", Menlo, Consolas, monospace—Monospace stack for code
--nk-shadow-menurgba(15,15,15,0.05) 0px 0px 0px 1px, rgba(15,15,15,0.1) 0px 3px 6px, rgba(15,15,15,0.2) 0px 9px 24pxrgba(15,15,15,0.2) 0px 0px 0px 1px, rgba(15,15,15,0.4) 0px 3px 6px, rgba(15,15,15,0.6) 0px 9px 24pxThree-layer shadow for every floating surface
--nk-shadow-card0 1px 2px rgba(0,0,0,0.04)noneBoard card lift. `none` in dark mode
--nk-shadow-btninset 0 0 0 1px rgba(15,15,15,0.1), 0 1px 2px rgba(15,15,15,0.1)inset 0 0 0 1px rgba(255,255,255,0.1), 0 1px 2px rgba(0,0,0,0.3)The white button’s outline: a 1px inset ring plus a 1px drop, no border
--nk-shadow-knob0 1px 2px rgba(0,0,0,0.2)—Switch knob
--nk-shadow-segment0 1px 2px rgba(0,0,0,0.08)—Active segment

Re-branding is one declaration. Everything derived — the focus ring, the checked states, the selected model card, the danger hover — is mixed from a token with color-mix(), so it follows along:

html:root { --nk-accent: #16a34a; }

Theming

Switch by setting one attribute. Nothing else moves.

html<html data-theme="light"> <!-- default, the attribute may be omitted --> <html data-theme="dark">
htmldocument.documentElement.setAttribute('data-theme', 'dark');

The token blocks also set color-scheme, so browser-drawn UI — date pickers, number spinners, select popups, scrollbars, autofill — follows the theme without any extra work.

To see re-branding in action, use the 🎨 button in the top bar: it applies the example palettes from theme-override.css (Forest, Slate, Sunset, High contrast) live on this page. The demo app has the same switch under Settings → Appearance.

State classes

NotionKit ships states, not behaviour. Add and remove these classes yourself; there is no JavaScript in the library.

ClassApplies toEffect
[hidden][class*="nk-"] ::slotted(*)Hides, always – also on components that set their own display, which beat the browser rule before 1.5.2. hidden="until-found" keeps the browser’s find-in-page reveal.
.activenk-tree-item nk-db-tab nk-tab nk-tab-bar-item nk-settings-pane nk-segmented button nk-emoji-cats span nk-board nk-bubble-menu buttonMarks the current item. Tree items and view tabs get the active background, .nk-tab gets the underline, panes become visible.
.opennk-modal-backdrop nk-cmdk-backdrop nk-sheet-backdrop nk-dialog-backdrop nk-peek-backdrop nk-pop.floating nk-tooltip nk-sidebar nk-sidebar-backdrop nk-toggle-arrowFades the overlay in and makes it interactive; slides the side peek in from the right; shows a floating menu or a tooltip; slides the sidebar in as a drawer on a phone; rotates the toggle arrow by 90°.
.peek-insetnk-appWhile the side peek is open: the main column gets a right padding as wide as the peek, so the page moves aside instead of lying under it. Desktop only.
.copiednk-copy-field .cf-btnThe moment after copying: the action turns green. Set it after the clipboard write and take it off again.
.squarenk-profile-row .big-avatarThe rounded square of a workspace icon instead of the circle of a person.
.floatingnk-popA menu over the page: invisible and inert until .open, then it fades in and settles from 4px higher and 98 %, the way the palette opens.
.sheetnk-popBelow 860px the popover becomes a bottom sheet – full width, grabber, 40px rows, the page dimmed – and inline positioning is overruled.
.done / currentnk-stepA finished step on the green tag with a check; the step you are on, ringed in the accent, with aria-current="step".
.collapsednk-tree-children nk-sidebar nk-sidebar-toggleFolds a subtree away with display: none. On the sidebar it slides it out to the left on the desktop; on the ☰ it shows the button that brings it back.
.selectednk-cmdk-item nk-model-card nk-slash-item nk-calendar .cal-dayThe keyboard-highlighted or chosen option – on a calendar the chosen day, in the accent. Distinct from active: selection is transient, active is where you are.
.start / end / in-rangenk-calendar .cal-dayA range: its first and last day in the accent, rounded on the outer side, the days between in the accent’s tint. A range of one day is start and end.
.today / off / outnk-calendar .cal-day nk-calendar-view .cv-dayToday in red – on the calendar view a red pill; a day not worked (weekend, holiday) greyed or washed; a day of the month before or after, the same.
.weeksnk-calendar nk-calendar-viewAdds the column of ISO calendar weeks in front of the days.
.stackednk-fieldLabel above a full-width control instead of beside it – textareas, long descriptions.
.compactnk-field nk-tree-item nk-selectThe tighter variant: a field with a small tertiary label and no row padding (inside .nk-fields), a 26px tree row, a 120px select.
.scrollnk-segmentedScrolls the segments horizontally with the scrollbar hidden, capped at the parent width.
.wrapnk-segmented nk-table nk-table tdLets the segments wrap onto further rows; on a table or a cell, lets cell text break instead of staying on one line.
.fixednk-tab-barPins the tab bar to the viewport bottom; a following .nk-tab-bar-spacer keeps its height in the flow.
:indeterminatenk-check input[type=checkbox]Mixed: some of a group chosen – the box fills with the accent and shows a dash. A property only, set by script; a radio group without a choice keeps its empty circles.
.coverednk-pageThe page follows a cover: no top padding, the icon overlaps the cover’s bottom edge. Class markup gets this from .nk-cover + .nk-page; sets the class.
.fullnk-pagePage option “Full width”: lifts the 760px cap, the column fills the window. Per page, as in Notion’s ⋯ menu.
.smallnk-page nk-avatar nk-btnPage option “Small text”: document text 14px instead of 16px; heading, lead, prose and editor follow. On an avatar 20px, on a button 24px.
.widenk-progress nk-input nk-select nk-textareaFills its row. A wide progress bar grows from 60px in a flex row, so its label stays beside it.
.bubblenk-ai-msgThe message as a grey bubble without avatar or name; with .user it sits on the right, as your own question does in Notion’s AI chat.
.lastnk-list-item nk-member-rowDrops the row’s bottom line where the list cannot see the last child – a row rendered in its own shadow root.
.alwaysnk-tab-barShows the tab bar at every width, not only below 860px – for previews and phone frames.
.shownk-toastSlides the toast up from below and fades it in.
.aria-checked="true"nk-switch (button form)Fills the track with the accent and slides the knob. An attribute, not a class, so the state is also announced to assistive technology.
:checkednk-todo input nk-check input nk-switch (input form)Native state. Draws the custom checkmark or dot and strikes a to-do label through.
.nk-drop-targetnk-block-hostDrag feedback: a 2px accent line above the block.

Contrast

Measured contrast ratios for the default palette against the surface each pair actually sits on. Body text clears WCAG AA comfortably in both themes. Secondary and tertiary text, the tag pairs, the primary button label and the danger colour sit below 4.5:1 — these are Notion’s own values, kept deliberately.

PairLightDarkAA body text
--nk-text / --nk-bg13.9811.78✓
--nk-text / --nk-bg-sidebar13.1811.06✓
--nk-text-sidebar / --nk-bg-sidebar6.117.03✓
--nk-text-secondary / --nk-bg4.274.63✗
--nk-text-tertiary / --nk-bg3.162.91—
--nk-accent / --nk-bg3.885.83—
--nk-on-accent / --nk-accent3.883.01✗
--nk-danger / --nk-bg4.884.63✓
tag gray10.179.10—
tag blue10.127.56—
tag green10.416.37—
tag orange10.185.49—
tag yellow11.304.42—
tag purple10.048.59—
tag red10.747.30—

If your project needs AA everywhere, theme-override.css ships a ready high-contrast block: uncomment it and the failing pairs are lifted, with the rest of the design untouched.

Editor integration

NotionKit ships no editor. What it does ship is .nk-block-host — the optical shell — plus a themed adapter layer scoped inside it. The integration contract is one class on the host element.

TipTap / ProseMirror — the vanilla-JS path

Load TipTap from a CDN as an ES module and mount it into an element carrying nk-block-host. The adapter rules do the rest: typography, headings, lists, quotes, code, links, selection and the placeholder are all styled from --nk-* tokens.

html<div class="nk-block-host" id="editor"></div> <script type="module"> import { Editor } from 'https://esm.sh/@tiptap/core@2'; import StarterKit from 'https://esm.sh/@tiptap/starter-kit@2'; import Placeholder from 'https://esm.sh/@tiptap/extension-placeholder@2'; new Editor({ element: document.getElementById('editor'), extensions: [StarterKit, Placeholder.configure({ placeholder: 'Type "/" for commands …' })], content: '<p>Hello NotionKit.</p>', }); </script>

BlockNote — the React path

Same idea: render the BlockNote view inside a block host. .bn-container, .bn-editor, .bn-block-content, the side menu and the drag handle are all covered, and .bn-suggestion-menu picks up the slash-menu look automatically.

html<div class="nk-block-host"> <BlockNoteView editor={editor} theme="light" /> </div>

Novel — the Next.js path

Novel builds on ProseMirror, so the TipTap rules already apply. .novel-editor and .novel-prose get the font and colour, and the prose width cap is released so the page column governs it.

html<div class="nk-block-host"> <EditorContent className="novel-editor" /> </div>

Live: TipTap running inside a block host

The editor below is real TipTap, loaded from a CDN, with no CSS of its own. Hover a block for the + / ⠿ rail and drag it, select text for the bubble toolbar, press / for the slash menu, and switch the theme — everything you see comes from the adapter section of notionkit.css.

The full recipe — slash menu on @tiptap/suggestion, bubble toolbar, block handle with drag & drop and a block menu — is docs-editor.js, about 300 lines of plain JavaScript. It puts NotionKit classes on its DOM (nk-slash-menu, nk-bubble-menu, nk-block-actions, nk-menu) and lets the stylesheet do the rest.

Web components

The stylesheet ships split in two so web components can adopt the component rules without re-declaring tokens inside their shadow roots.

htmlimport { componentsSheet, tokensCss } from '@jungherz-de/notionkit/notionkit-styles.js'; // Tokens go on the document once, wrapped in a layer so a brand // stylesheet wins no matter the load order. const tokens = new CSSStyleSheet(); tokens.replaceSync(`@layer notionkit-defaults { ${tokensCss} }`); document.adoptedStyleSheets = [...document.adoptedStyleSheets, tokens]; class NkCallout extends HTMLElement { constructor() { super(); const root = this.attachShadow({ mode: 'open' }); root.adoptedStyleSheets = [componentsSheet]; // components only } }

Why the split matters

A shadow root that adopts the whole sheet also adopts the :root and [data-theme] blocks. Inside that root the selectors match the element’s own theme wrapper, so every token is re-declared locally — and a matching rule always beats an inherited value. A consumer’s own :root { --nk-accent: … } would then never arrive. Adopt componentsSheet only, and put tokensCss on the document.

The ::slotted() twins

Content passed in from outside stays in the light DOM and is no descendant of anything in the shadow tree, so .nk-callout .c-icon cannot reach it. Every such rule therefore has a ::slotted() twin beside it — 92 of them, including a [slot="…"] twin for every named slot of NotionKit Elements. Two limits: ::slotted() matches only the assigned node, never inside it (pass icons directly, never wrapped), and for slotted content the outer tree wins over the shadow tree.

html.nk-callout .c-icon, .nk-callout ::slotted(.c-icon) { font-size: 17px; }

A working proof of concept ships as elements-poc.html — an <nk-callout> and an <nk-tag color="…"> that adopt componentsSheet, re-theme through data-theme and take the page’s branding. The page measures all of that live (it needs a build and a server: npm run build, then serve the folder).

App shell & layout

PRD 5.1

App shell .nk-app

nk-app is a full-height flex row: sidebar left, main column right. It is the outermost element of a workspace app and the only place a fixed height belongs. Inside the sidebar, nk-sidebar-scroll is the scrolling tree area and nk-sidebar-footer the pinned bottom (Settings, Trash). On the desktop the sidebar collapses as in Notion: .nk-sidebar-head holds the workspace row and the « .nk-sidebar-collapse, which shows while the pointer is over the sidebar; .collapsed on the sidebar slides it out to the left – the main column takes the width, the hidden sidebar takes no focus – and .collapsed on the topbar's .nk-sidebar-toggle shows the ☰ that brings it back. Notion's shortcut is ⌘\.

📊 Project overview

Product roadmap

A short lead paragraph sets up the page in a calmer, larger type size before the body copy begins.

html<div class="nk-sidebar-backdrop open"></div>   <!-- phones: the drawer's scrim -->
<aside class="nk-sidebar open">…</aside>         <!-- slides in over the page -->
<button class="nk-topbar-btn nk-sidebar-toggle" aria-label="Menu">☰</button>
jscollapseBtn.addEventListener('click', () => {            // the « in .nk-sidebar-head
  sidebar.classList.add('collapsed');                      // slides out on the desktop
  sidebarToggle.classList.add('collapsed');                // the ☰ in the topbar brings it back
});
html<div class="nk-app" style="height:100%"> <aside class="nk-sidebar"> <div class="nk-sidebar-head"> <div class="nk-workspace"><div class="avatar">A</div>Acme Inc<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> <div class="nk-sidebar-scroll"> <div class="nk-tree-item"><span class="icon">🔍</span><span class="label">Search</span></div> <div class="nk-tree-item active"><span class="icon">🏠</span><span class="label">Home</span></div> <div class="nk-tree-item"><span class="icon">📥</span><span class="label">Inbox</span></div> </div> <div class="nk-sidebar-footer"> <div class="nk-tree-item"><span class="icon">⚙️</span><span class="label">Settings</span></div> </div> </aside> <main class="nk-main"> <header class="nk-topbar"> <button class="nk-topbar-btn nk-sidebar-toggle" aria-label="Menu">☰</button> <div class="nk-breadcrumb"><span class="crumb current">📊 Project overview</span></div> <div class="nk-topbar-actions"><button class="nk-topbar-btn nk-share-btn">Share</button></div> </header> <div class="nk-page-scroll"><div class="nk-page" style="padding-top:16px"> <h1 class="nk-page-title" style="font-size:28px">Product roadmap</h1> <p class="lead">A short lead paragraph sets up the page in a calmer, larger type size before the body copy begins.</p> </div></div> </main> </div>
On a small screen:Below 860px the sidebar steps aside and the main column takes the full width. .open brings it back as an off-canvas drawer over the page, with a .nk-sidebar-backdrop as its scrim: it slides in and the scrim fades, CSS only. The ☰ that opens it is a .nk-topbar-btn.nk-sidebar-toggle at the start of the topbar, shown on phones only; toggle .open on sidebar and backdrop, close on the backdrop, Escape and a chosen page. The height is 100dvh with a 100vh fallback, so a standalone PWA on iOS does not count the status bar into the shell. Safe areas are handled per surface, not on nk-app: topbar, page, sidebar and tab bar pad their content by the left/right insets (Dynamic Island in landscape) while their backgrounds run edge to edge.
.nk-app.nk-sidebar.nk-sidebar-head.nk-sidebar-collapse.nk-sidebar-scroll.nk-sidebar-footer.nk-main.nk-sidebar-backdrop.nk-sidebar-toggle.open.collapsed

Workspace switcher .nk-workspace

Sits at the very top of the sidebar. The avatar gradient is mixed from --nk-decor-purple and --nk-decor-blue, so it survives a re-brand untouched.

A
Acme Inc⌄
html<div class="nk-workspace" style="background:var(--nk-bg-sidebar);max-width:244px"> <div class="avatar">A</div>Acme Inc<span class="chev">⌄</span> </div>
On a small screen:Hidden together with the sidebar below 860px.
.nk-workspace.avatar.chev

Topbar .nk-topbar

A 44px-high row holding the breadcrumb on the left and actions on the right. nk-topbar-actions pushes itself right with margin-left:auto, so you never need a spacer. Passive text among the actions – "Edited 2 min ago" – is nk-topbar-meta. nk-theme-glyph wraps the appearance glyph of a theme button: one emoji for a fixed theme, with auto sun and moon diagonally for the mode that follows the system.

📊 Project overview/🚀 Roadmap
Last edited 2 min ago
html<header class="nk-topbar" style="border:1px solid var(--nk-border);border-radius:var(--nk-radius)"> <div class="nk-breadcrumb"> <span class="crumb">📊 Project overview</span><span class="sep">/</span><span class="crumb current">🚀 Roadmap</span> </div> <div class="nk-topbar-actions"> <span class="nk-topbar-meta">Last edited 2 min ago</span> <button class="nk-topbar-btn">💬</button> <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> <button class="nk-topbar-btn nk-theme-toggle" title="Appearance: automatic"><span class="nk-theme-glyph auto"><span>☀️</span><span>🌙</span></span></button> <button class="nk-topbar-btn">⋯</button> </div> </header>
On a small screen:Below 860px the topbar keeps the page and its actions, as Notion's mobile app does: only the last crumb stays and ends in an ellipsis, nk-topbar-meta hides.
.nk-topbar.nk-topbar-actions.nk-topbar-btn.nk-topbar-meta.nk-share-btn.nk-theme-toggle.nk-theme-glyph

Tab bar (mobile) .nk-tab-bar

The thumb-reachable twin of the sidebar for phones and installed PWAs: up to five destinations in a row, each an icon over a short label, the current one in --nk-accent. Put it last inside nk-main – it sits below the scrolling page and never moves. In a document that scrolls itself it sticks to the viewport bottom. floating makes it a capsule; fixed pins it to the viewport bottom for standalone PWAs, with a .nk-tab-bar-spacer right after it keeping its height (--nk-tab-bar-height plus the safe area) in the flow. The fifth item usually opens the sidebar as a drawer.

html<nav class="nk-tab-bar fixed">…</nav><div class="nk-tab-bar-spacer"></div>
html<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> <button class="nk-tab-bar-item"><span class="icon">🔍</span><span class="label">Search</span></button> <button class="nk-tab-bar-item"><span class="icon">⚙️</span><span class="label">Settings</span></button> <button class="nk-tab-bar-item"><span class="icon">☰</span><span class="label">More</span></button> </nav> </div> <pre class="nk-code" style="margin-top:12px"><span class="lang">html</span>&lt;nav class="nk-tab-bar fixed"&gt;…&lt;/nav&gt;&lt;div class="nk-tab-bar-spacer"&gt;&lt;/div&gt;</pre>
On a small screen:Visible only below 860px – above, the sidebar takes over and the bar is display: none. always shows it at every width, as in this preview. On phones with a home indicator the bottom padding is the larger of 6px and env(safe-area-inset-bottom), so the labels end where iOS ends its own tab bar; in landscape the side padding grows to the left/right insets (Dynamic Island) while the background still runs edge to edge.
.nk-tab-bar.nk-tab-bar-item.nk-tab-bar-spacer.icon.label.active.always.fixed.floating

Breadcrumb .nk-breadcrumb

Each step is a .crumb; the last one carries .current and turns from secondary to primary text. Separators are .sep.

📊 Project overview/ 📚 Knowledge base/ 🚀 Roadmap
html<div class="nk-breadcrumb"> <span class="crumb">📊 Project overview</span><span class="sep">/</span> <span class="crumb">📚 Knowledge base</span><span class="sep">/</span> <span class="crumb current">🚀 Roadmap</span> </div>
On a small screen:Wraps rather than truncating. Shorten the trail server-side on small screens.
.nk-breadcrumb.crumb.sep.current

Section label .nk-section-label

The small uppercase caption between sidebar groups. Its .plus affordance only appears on hover — a quiet way to keep an add action reachable without decorating the rail.

📊Project overview
html<div style="background:var(--nk-bg-sidebar);border-radius:var(--nk-radius);padding:4px 8px;max-width:244px"> <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></div> </div>
On a small screen:Hidden inside the settings nav below 860px, where the nav collapses to icons.
.nk-section-label.plus

Page shell & document

PRD 5.3

Page column & page options .nk-page

The document column: max-width: 760px with auto margins, never a fixed width. After a cover (.nk-cover + .nk-page, or .nk-page.covered) the icon pulls itself up over it with a negative margin and the page has no top padding; without one the page keeps 24px top padding and the icon sits inside it, fully visible. The title is contenteditable-ready. Notion's two page options are classes on the page: full lifts the 760px cap so the column fills the window, small sets the document text from 16px to 14px, and headings, lead, prose and editor follow because they are sized in em. Title, properties, tables and controls keep their size. In Notion these are options of the single page, in its ⋯ menu, not app settings. The token --nk-page-full-max caps full – none by default; 1080px on :root gives a dashboard of tables room without 1600px lines.

🚀

Product roadmap

👤 Ada Lovelace📅 Created 12 May 2026

A short lead paragraph sets up the page in a calmer, larger type size before the body copy begins.

html<div class="nk-page-scroll" style="height:100%"> <div class="nk-cover" style="height:120px"></div> <div class="nk-page" style="padding-bottom:24px"> <div class="nk-page-icon">🚀</div> <h1 class="nk-page-title" contenteditable="true">Product roadmap</h1> <div class="nk-page-meta"><span>👤 Ada Lovelace</span><span>📅 Created 12 May 2026</span></div> <p class="lead">A short lead paragraph sets up the page in a calmer, larger type size before the body copy begins.</p> </div> </div>
On a small screen:Side padding drops from 64px to 24px below 860px. The 760px cap simply never binds, so full changes nothing there; small still does.
.nk-page-scroll.nk-page.nk-page-icon.nk-page-title.nk-page-meta.covered.full.small

Page properties .nk-props

The properties under the title of a database page – the pattern Notion is known for. One .nk-prop per row: the name with its type icon in a 160px column, the value beside it, both 34px tall with the hover wash, as each half opens its own editor in Notion. Values are ordinary markup: tags, an avatar with a name, a date, a .nk-progress.wide. It replaces .nk-page-meta on a page that is a row. Written as <dl>, <dt> and <dd> it is a description list for assistive technology as well; plain <div>s work the same. A value that is text – a sentence, an address, a model name with a tag – takes .text and flows as text instead of setting its parts one under the other. The value keeps at least 220px: in a column narrower than 380px, a side panel say, it moves under its name. .flush drops the outer margin, for a list inside a panel or a flex column with a gap of its own.

◉Status
In progress
👤Owner
ALAda Lovelace
📅Due
2 June 2026
✉️Address
support+design@notionkit.example.com · Verified
🏷️Tags
Design systemCSS
▰Progress
65 %
html<dl class="nk-props" style="max-width:520px"> <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> <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> <div class="nk-prop"><dt class="p-name"><span class="p-icon">📅</span>Due</dt><dd class="p-value">2 June 2026</dd></div> <div class="nk-prop"><dt class="p-name"><span class="p-icon">✉️</span>Address</dt><dd class="p-value text">support+design@notionkit.example.com · <span class="nk-tag green">Verified</span></dd></div> <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> <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> </dl>
On a small screen:Below 860px each property stacks: the name above its value, both flush left, so the value gets the whole width.
.nk-props.nk-prop.p-name.p-icon.p-value.text.flush

Cover .nk-cover

A decorative band above the page, 200px tall – the token --nk-cover-height, on :root or on one cover; Notion's own takes about 30vh, clamp(200px, 30vh, 300px) comes close. Three radial gradients mixed from the --nk-decor-* tokens over --nk-bg-callout. For a picture put an <img> inside: it fills the band and is cropped, never stretched, and object-position moves the crop, as Notion's “Reposition” does. The gradient stays underneath while it loads.

html<div class="nk-cover" style="border-radius:var(--nk-radius)"></div> <div class="nk-cover" style="height:120px;border-radius:var(--nk-radius);margin-top:12px"><img src="covers/meadow.svg" alt=""></div>
On a small screen:Full bleed, as tall as --nk-cover-height; lower the token if the band eats too much of a short screen. Without a page icon the page keeps its top padding under the cover.
.nk-cover

Headings & lead .nk-heading

nk-heading is the in-document section heading — a flex row, so an emoji sits on the baseline without extra markup. p.lead is the larger intro paragraph inside nk-page.

✅ How this works

A short lead paragraph sets up the page in a calmer, larger type size before the body copy begins.

html<div class="nk-page" style="padding:0;max-width:none"> <h2 class="nk-heading">✅ How this works</h2> <p class="lead">A short lead paragraph sets up the page in a calmer, larger type size before the body copy begins.</p> </div>
On a small screen:Unchanged. The page title stays 40px; override it if that is too loud on a phone.
.nk-heading.lead

Content elements

PRD 5.4

Callout .nk-callout

A tinted block for the one thought that must not be missed. The icon is a .c-icon child and has a ::slotted() twin, so <nk-callout> can accept it through a slot.

💡
Core idea: a callout carries one thought that should not be missed. Keep it to a sentence or two.
html<div class="nk-callout"><span class="c-icon">💡</span><div><b>Core idea:</b> a callout carries one thought that should not be missed. Keep it to a sentence or two.</div></div>
On a small screen:Flows naturally; the icon stays on the first line because the row is align-items: flex-start.
.nk-callout.c-icon

Bookmark .nk-bookmark

Notion's link block: .bm-title, two lines of .bm-desc and the .bm-url with its .bm-favicon on the left, the preview image in .bm-cover on the right – a third of the box, 240px at most, so a 1200×630 og:image fits nearly whole. The whole box is the link, an <a>; fill it from the page's og:title, og:description and og:image. Without an image the text takes the width, without a description the box gets lower. Also the way to show how a shared link will look.

html<a class="nk-bookmark" href="https://notionkit.jungherz.com" target="_blank" rel="noopener" style="max-width:600px"> <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>
On a small screen:The image keeps its third and is cropped at the centre; title and address end in an ellipsis, the description in two lines.
.nk-bookmark.bm-text.bm-title.bm-desc.bm-url.bm-favicon.bm-cover

To-do .nk-todo

A checkbox with a custom checkmark. The sibling selector input:checked + span strikes the label through — both halves live inside one component, so it survives the move into a shadow root.

html<div> <label class="nk-todo"><input type="checkbox" checked><span>Sketch the component scope</span></label> <label class="nk-todo"><input type="checkbox" checked><span>Build the preview</span></label> <label class="nk-todo"><input type="checkbox"><span>Prioritise the MVP</span></label> </div>
On a small screen:The 16px box is below the touch minimum. Wrap it in a <label> so the whole row is tappable.
.nk-todo

Toggle .nk-toggle

Built on native <details>/<summary>, so it opens and closes without a line of JavaScript. The marker is a ::before that rotates on [open].

What is not included?
The text editor itself. NotionKit ships the optical shell; you mount TipTap, BlockNote or Novel inside it.
html<details class="nk-toggle" open> <summary>What is not included?</summary> <div class="toggle-body">The text editor itself. NotionKit ships the optical shell; you mount TipTap, BlockNote or Novel inside it.</div> </details>
On a small screen:The summary row is comfortably tappable. Native behaviour on all platforms.
.nk-toggle.toggle-body

Quote & divider .nk-quote

A block quote with a solid left rule, plus the horizontal divider. nk-divider is meant for an <hr> and resets the element’s own border.

Design is not just what it looks like. Design is how it works.— Steve Jobs

html<blockquote class="nk-quote">Design is not just what it looks like. Design is how it works.<cite class="q-cite">— Steve Jobs</cite></blockquote> <hr class="nk-divider">
On a small screen:Unchanged.
.nk-quote.q-cite.nk-divider

Inline mentions .nk-mention

Three variants inside running text: .person with a mini avatar, .page underlined in a hairline, .date in the orange tag colour. All are inline-flex and never break mid-mention.

SLSara Lindt 📄 Knowledge base 📅 20 May

html<p style="line-height:1.9"> <span class="nk-mention person"><span class="nk-avatar purple">SL</span>Sara Lindt</span> <span class="nk-mention page">📄 Knowledge base</span> <span class="nk-mention date">📅 20 May</span> </p>
On a small screen:white-space: nowrap keeps each mention whole; the paragraph wraps around it.
.nk-mention.person.page.date.mini-avatar

Code .nk-code

A block with a language badge in the corner and two colour hooks — .tag takes the accent, .attr the orange tag colour. nk-inline-code is the in-sentence variant.

html<div class="nk-callout"> <span class="c-icon">💡</span> </div>

Inline: --nk-accent

html<div class="nk-code"><span class="lang">html</span><span class="tag">&lt;div</span> <span class="attr">class=</span>"nk-callout"<span class="tag">&gt;</span> <span class="tag">&lt;span</span> <span class="attr">class=</span>"c-icon"<span class="tag">&gt;</span>💡<span class="tag">&lt;/span&gt;</span> <span class="tag">&lt;/div&gt;</span></div> <p>Inline: <code class="nk-inline-code">--nk-accent</code></p>
On a small screen:white-space: pre plus overflow-x: auto: long lines scroll inside the block instead of pushing the page sideways.
.nk-code.lang.tag.attr.nk-inline-code

Prose .nk-prose

Text the app did not write tag by tag – rendered Markdown, help pages, the HTML an editor saved – takes the document look from one class on its container: headings, paragraphs, lists, task lists, links, inline code, code blocks, quotes, rules, pictures and tables. It reads the very same rules as the editor adapter, so a page shown for reading and the same page in TipTap match to the pixel. Sizes are in em and follow what the text sits in: 16px on a page, 14px with small or in the app chrome. No outer margin on the first and last block, so it sits flush in a panel. An empty paragraph keeps its line, as it had it in the editor.

Release notes

Rendered Markdown takes the document look from one class: headings, lists, links, code, quotes and tables.

  • Paragraphs and lists keep Notion’s spacing
  • Sizes follow the page – 16px, or 14px with small
The editor adapter reads the same rules.
ClassFor
fullFull width
smallSmall text

Class only, on purpose: ::slotted() reaches the slotted node and never a paragraph inside it, so a <nk-prose> element could not style its content. Put the class on a light-DOM container, also inside NotionKit Elements.

html<div class="nk-page" style="padding:0;max-width:560px;margin:0"><div class="nk-prose"> <h2>Release notes</h2> <p>Rendered Markdown takes the document look from <strong>one class</strong>: headings, lists, <a href="#">links</a>, <code>code</code>, quotes and tables.</p> <ul><li>Paragraphs and lists keep Notion’s spacing</li><li>Sizes follow the page – 16px, or 14px with <code>small</code></li></ul> <blockquote>The editor adapter reads the same rules.</blockquote> <table><thead><tr><th>Class</th><th>For</th></tr></thead><tbody><tr><td><code>full</code></td><td>Full width</td></tr><tr><td><code>small</code></td><td>Small text</td></tr></tbody></table> </div></div>
On a small screen:Flows with the column; a wide table scrolls in its own box instead of widening the page.
.nk-prose

Database views

PRD 5.5

View tabs .nk-db-tabs

The strip above a database. The active view sits on the active wash as a pill, the others are plain words — no underline, no rule, as in Notion since 2025. The .badge child carries the row count.

▦ Table4
▤ Board
🖼 Gallery
html<div class="nk-database"> <div class="nk-db-tabs"> <div class="nk-db-tab active">▦ Table<span class="badge">4</span></div> <div class="nk-db-tab">▤ Board</div> <div class="nk-db-tab">🖼 Gallery</div> </div> </div>
On a small screen:Add overflow-x: auto to the strip when you have more than three or four views.
.nk-database.nk-db-tabs.nk-db-tab.active.badge

Toolbar & filter pills .nk-db-toolbar

The database header as Notion lays it out: view tabs on the left, the view's tools on the right – .nk-db-tool buttons for Filter, Sort and search, then “New”. A tool in effect takes .active, the accent. Under it, .nk-filter-row holds the filters in effect as .nk-filter-pills – .active with an accent tint – and a quiet .add pill at the end. A pill is one button, or a box with the label button and a .fp-remove × when a filter can be removed in place.

▦ Table
▤ Board
☰ List
html<div class="nk-database" style="max-width:620px"> <div class="nk-db-toolbar"> <div class="nk-db-tabs"> <div class="nk-db-tab active">▦ Table</div><div class="nk-db-tab">▤ Board</div><div class="nk-db-tab">☰ List</div> </div> <div class="tools"> <button class="nk-db-tool active">Filter</button><button class="nk-db-tool">Sort</button><button class="nk-db-tool" aria-label="Search">🔍</button> <button class="nk-btn primary small">New</button> </div> </div> <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">Owner: Marcel ▾</button> <button class="nk-filter-pill add">+ Filter</button> </div> </div>
On a small screen:The tabs scroll sideways when the row gets narrow; the tools keep their place. The pills wrap onto further rows.
.nk-db-toolbar.tools.nk-db-tool.active.nk-filter-row.nk-filter-pill.add.fp-remove

Table view .nk-table

36px rows at 14px, hairlines between rows and columns, header cells quiet and clickable — measured on a live Notion table. A selection column has th.select-all for a header: a checkbox instead of a name – empty, a dash for some rows, a check for all – that sorts nothing; the dash is indeterminate, set by script. Every cell is white-space: nowrap so columns keep their shape; .wrap on the table or on a cell lets text break, like Notion's “wrap column”. .num on a cell sets a number right-aligned in figures of equal width, as Notion does with a number property; the header stays left. .row-actions in a cell sets buttons for the row at its right edge – “Retry”, “Discard” – and th.actions is a header that sorts nothing. .td-text gives a value one of Notion's nine text colours – .orange for an error – and .td-desc a quiet second line under it; the row grows with it. .nk-new-row is the add affordance at the bottom (inside .nk-table the short form .new-row still works).

📄Name◉Status 👤Owner📅Due 📊Progress#Effort (h)
🚀 RoadmapThree open questionsDone SLSara 12.05.2026 100 %6.0
🎨 Design systemIn progress TWTom 20.05.2026 65 %12.5
📣 LaunchPlanned MKMia 02.06.2026 10 %3.25
+ New page
html<div class="nk-table-wrap"><table class="nk-table"> <thead><tr> <th class="select-all"><label class="nk-check"><input type="checkbox" aria-label="Select all"></label></th><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><th class="actions"></th> </tr></thead> <tbody> <tr><td><label class="nk-check"><input type="checkbox" checked></label></td><td><span class="row-title">🚀 Roadmap</span><span class="td-desc">Three open questions</span></td><td><span class="nk-tag green">Done</span></td> <td><span class="person-cell"><span class="nk-avatar small purple">SL</span>Sara</span></td> <td class="date-cell">12.05.2026</td> <td><span class="nk-progress"><i style="width:100%"></i></span><span class="nk-progress-label">100 %</span></td><td class="num">6.0</td> <td><span class="row-actions"><button class="nk-btn secondary small">Open</button><button class="nk-btn danger small">Archive</button></span></td></tr> <tr><td><label class="nk-check"><input type="checkbox"></label></td><td><span class="row-title">🎨 Design system</span></td><td><span class="nk-tag blue">In progress</span></td> <td><span class="person-cell"><span class="nk-avatar small blue">TW</span>Tom</span></td> <td class="date-cell">20.05.2026</td> <td><span class="nk-progress"><i style="width:65%"></i></span><span class="nk-progress-label">65 %</span></td><td class="num">12.5</td> <td><span class="row-actions"><button class="nk-btn secondary small">Open</button><button class="nk-btn danger small">Archive</button></span></td></tr> <tr><td><label class="nk-check"><input type="checkbox" checked></label></td><td><span class="row-title">📣 Launch</span></td><td><span class="nk-tag yellow">Planned</span></td> <td><span class="person-cell"><span class="nk-avatar small orange">MK</span>Mia</span></td> <td class="date-cell"><span class="td-text red">02.06.2026</span></td> <td><span class="nk-progress"><i style="width:10%"></i></span><span class="nk-progress-label">10 %</span></td><td class="num">3.25</td> <td><span class="row-actions"><button class="nk-btn secondary small">Open</button><button class="nk-btn danger small">Archive</button></span></td></tr> </tbody> </table> <div class="nk-new-row">+ New page</div></div> <script>document.currentScript.previousElementSibling.querySelector('th.select-all input').indeterminate = true</script>
On a small screen:This is the key one: nk-table-wrap scrolls horizontally so the table never forces the page wider. Always wrap the table.
.nk-table-wrap.nk-table.wrap.th-icon.row-title.date-cell.person-cell.num.row-actions.actions.select-all.td-text.td-desc.nk-new-row

Tags .nk-tag

The select option as Notion draws it: 20px tall, 3px corners, the cell's 14px, near-black text on a saturated fill (≥ 10:1 in light mode). Nine colours, each a background/text pair per theme; without a colour class it is the grey tag. For coloured text use the --nk-color-* mid-tones, for a soft surface the --nk-tint-* backgrounds.

Not started Archive Planned Review Done In progress Design system Idea Blocked
html<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 system</span> <span class="nk-tag pink">Idea</span> <span class="nk-tag red">Blocked</span>
On a small screen:Unchanged. Inline-flex with an ellipsis, so a long option is cut rather than the column widened.
.nk-tag.gray.brown.orange.yellow.green.blue.purple.pink.red

Progress bar .nk-progress

A 6px rail whose fill is an <i> with a percentage width. Track and fill use Notion's blue tint and mid-tone, so a re-theme carries them along. 110px wide in a cell; wide fills its row – a property value, a panel, a course overview – and in a flex row the label keeps its place beside it; in a flex column it stays a 6px bar. .nk-progress-row holds a bar and its label as one row – in a flex column, a panel, the two would otherwise stand one under the other: the label sits beside the bar, on its middle, and a wide bar fills the rest.

65 %

20 %
80 %

Progress

40 %
html<span class="nk-progress"><i style="width:65%"></i></span><span class="nk-progress-label">65 %</span><br><br> <span class="nk-progress"><i style="width:20%"></i></span><span class="nk-progress-label">20 %</span> <div style="display:flex;align-items:center;max-width:360px;margin-top:14px"><span class="nk-progress wide"><i style="width:80%"></i></span><span class="nk-progress-label">80 %</span></div> <div class="nk-panel" style="max-width:360px;margin-top:14px"><h3>Progress</h3><span class="nk-progress-row"><span class="nk-progress wide"><i style="width:40%"></i></span><span class="nk-progress-label">40 %</span></span></div>
On a small screen:The 110px bar stays legible in a table cell; wide follows its container.
.nk-progress.nk-progress-label.wide.nk-progress-row

Board view .nk-board

Fixed 220px columns in a horizontally scrolling row. nk-board is display:none by default so it can sit next to a table view; add .active to show it.

Planned1
🖥 Board view
TW28.05.
In progress1
🎨 Design system
Done1
🚀 Roadmap
12.05.
html<div class="nk-board active"> <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"><div class="card-title">🖥 Board view</div><div class="card-meta"><span class="nk-avatar small blue">TW</span>28.05.</div></div> </div> <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"><div class="card-title">🎨 Design system</div><div class="card-meta"><span class="nk-progress" style="width:70px"><i style="width:65%"></i></span></div></div> </div> <div class="nk-board-col"> <div class="nk-board-col-header"><span class="nk-tag green">Done</span><span class="count">1</span></div> <div class="nk-card"><div class="card-title">🚀 Roadmap</div><div class="card-meta">12.05.</div></div> </div> </div>
On a small screen:Columns scroll horizontally rather than stacking — the board stays a board.
.nk-board.nk-board-col.nk-board-col-header.count.nk-card.card-title.card-meta

List view .nk-list

The third database view: one line per row – icon, title and a few properties on the right – as Notion shows a database under “List”. Hairlines between rows and the hover wash across the whole row, like a table without columns; the last row drops its line. As an <a> each row is a real link. Put it in .nk-database under the view tabs, or use it on its own for any list of pages. A row that is no target itself – a file with its own Remove button – takes .static: no hand, no hover wash.

html<div class="nk-list" style="max-width:520px"> <a class="nk-list-item" href="#"><span class="l-icon">🚀</span><span class="l-title">Roadmap</span><span class="l-meta">12.05.2026 <span class="nk-tag green">Done</span></span></a> <a class="nk-list-item" href="#"><span class="l-icon">🎨</span><span class="l-title">Design system</span><span class="l-meta">20.05.2026 <span class="nk-tag blue">In progress</span></span></a> <a class="nk-list-item" href="#"><span class="l-icon">📣</span><span class="l-title">Launch</span><span class="l-meta">02.06.2026 <span class="nk-tag orange">Planned</span></span></a> </div>
On a small screen:Stays one line per row: the title gives way first and ends in an ellipsis, the properties keep their place.
.nk-list.nk-list-item.l-icon.l-title.l-meta.last.static

Calendar view .nk-calendar-view

The database as Notion's month, a view tab like table, board and list: a head with the month and the date picker's .cal-nav buttons, the weekdays, and a .cv-day per day with its .cv-num top right – today's on a red pill – and the rows due that day as .cv-item cards. Days of the month before and after (.out) and days not worked (.off) are washed. weeks adds a column of ISO calendar weeks. The columns share the width, so seven always fit; an item's title ends in an ellipsis.

May 2026
W
Mon
Tue
Wed
Thu
Fri
Sat
Sun
18
27
28
29
30
1
2
3
19
4
5
6
7
8
9
10
20
11
12
13
14
15
16
17
21
18
19
20
21
22
23
24
22
25
26
27
28
29
30
31
html<div class="nk-calendar-view weeks"> <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> <div class="cv-grid"> <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> <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> <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</button></div> <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> <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><button class="cv-item">▤ Board 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> <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> </div> </div>
On a small screen:Below 860px the days get lower (64px) and the cards smaller; seven columns still fit.
.nk-calendar-view.weeks.cv-head.cv-title.cv-grid.cv-wd.cv-week.cv-day.out.off.today.cv-num.cv-item

Gallery view .nk-gallery

The fifth database view: the rows as cards with a picture on top, in a grid that fills the row – Notion's gallery, for a course catalog or a reading list. A card is the board's .nk-card with .card-title and .card-meta; a .nk-cover as its first child runs to the card's edges in 2:1, a picture inside cropped to fill – without one the cover gradient shows. .small and .large set the card size, columns from 180px and 340px instead of 260px; .fit shows a picture whole, for logos. A .nk-new-row at the end becomes the add card. Clicking a card is your script, as with a row of the table. A card to open in a new tab or to copy the address of is a link, <a class="nk-card" href>, in a .card-item that carries role="listitem" – the link keeps its own role – and the card fills it.

html<div class="nk-gallery" role="list"> <div class="nk-card" role="listitem" tabindex="0"><div class="nk-cover"><img src="covers/aurora.svg" alt=""></div><div class="card-title">🚀 Roadmap</div><div class="card-meta"><span class="nk-tag green">Done</span><span>📅 12.05.2026</span></div></div> <div class="nk-card" role="listitem" tabindex="0"><div class="nk-cover"><img src="covers/dunes.svg" alt=""></div><div class="card-title">🎨 Design system</div><div class="card-meta"><span class="nk-tag blue">In progress</span><span>📅 20.05.2026</span></div></div> <div class="card-item" role="listitem"><a class="nk-card" href="#nk-gallery"><div class="nk-cover"></div><div class="card-title">📣 Launch</div><div class="card-meta"><span class="nk-tag yellow">Planned</span><span>📅 02.06.2026</span></div></a></div> <div class="nk-new-row" role="button" tabindex="0">+ New page</div> </div>
On a small screen:The grid drops columns by itself – one card per row on a phone, no breakpoint involved.
.nk-gallery.small.large.fit.card-item

Forms & settings

PRD 5.6

Inputs, textarea, select .nk-input

One shared shape for all three: filled with --nk-bg-input inside a hairline, 32px tall at 14px — Notion's input, not an outlined white box. The focus ring is mixed from the accent, so it re-brands with it. .wide makes an input fill its row. Each keeps at least 210px, except inside a panel, where it never runs past the edge of a narrow tile; a field row keeps the 210px there too.

html<div style="display:flex;flex-direction:column;gap:10px;max-width:340px"> <input class="nk-input wide" value="Ada Lovelace"> <select class="nk-select wide"><option>Light</option><option>Dark</option><option>System</option></select> <textarea class="nk-textarea wide" placeholder="A few words about yourself …"></textarea> </div>
On a small screen:min-width: 210px can overflow a narrow field row — pair it with .wide or let nk-field wrap.
.nk-input.nk-textarea.nk-select.wide

Copy field .nk-copy-field

A value to take along – a link to share, an address, a key – in a field of its own, as tall as an input, with its actions inside on the right. .cf-value holds the text on one line with an ellipsis, and a click selects it whole; .cf-btns are the quiet actions – Copy and, for a secret, Show. .copied on a button is the moment after, in green; the copying is your script. mono for addresses, keys and code, wrap lets a long value break with the actions on its first line, wide fills the row. Like an input it keeps at least 210px, except inside a panel. A button may carry an <svg> beside its word in a <span> – two overlapping squares for Copy, an eye for Show, a check for the moment after – and the word as aria-label: on a phone, and anywhere with icons, the icon stands in for the word, as in Notion, and a masked key keeps a dozen characters more.

https://monahilft.notionkit.app
ntn_••••••••••••••••••••
ntn_••••••••••••••••••••
jsbtn.addEventListener('click', async () => {
  await navigator.clipboard.writeText(field.querySelector('.cf-value').textContent);
  btn.classList.add('copied');   // green for a moment
  setTimeout(() => btn.classList.remove('copied'), 1500);
});
html<div class="nk-copy-field" style="max-width:340px"><span class="cf-value">https://monahilft.notionkit.app</span><button class="cf-btn">Copy</button></div> <div class="nk-copy-field mono" style="max-width:340px;margin-top:10px"><span class="cf-value">ntn_••••••••••••••••••••</span><button class="cf-btn">Show</button><button class="cf-btn copied">✓ Copied</button></div> <div class="nk-copy-field mono icons" style="max-width:340px;margin-top:10px"><span class="cf-value">ntn_••••••••••••••••••••</span><button class="cf-btn" aria-label="Show"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7S2 12 2 12Z"/><circle cx="12" cy="12" r="3"/></svg><span>Show</span></button><button class="cf-btn" aria-label="Copy"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M15 9V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h4"/><rect x="9" y="9" width="12" height="12" rx="2"/></svg><span>Copy</span></button><button class="cf-btn copied" aria-label="Copied"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M20 6 9 17l-5-5"/></svg><span>Copied</span></button></div>
On a small screen:Keeps to its column: the value is cut, never the actions. Buttons that carry an icon show it instead of their word.
.nk-copy-field.cf-value.cf-btn.copied.mono.wrap.wide.icons

Date picker .nk-calendar

Notion's month sheet for a date: a title with Today and ‹ ›, the weekdays, six rows of .cal-day buttons. The chosen day is .selected in the accent, today is red; a range runs from .start to .end over .in-range days in the accent's tint. .off greys a day that is not worked – a weekend, a holiday, with its name as title – and .out a day of the month before or after; aria-disabled="true" marks one outside the bounds. .cal-marks holds up to three dots in the nine colours (i.blue, i.red …), for deadlines or milestones. weeks adds the ISO calendar week in front of each row, .cal-foot a time field or actions under the sheet. Cells are 36px, so seven and the week column fill a .nk-pop; put it in .nk-pop.floating.sheet and it is a popover on the desktop and a sheet with 44px cells on a phone. The month, the keys and the value are your script – or <nk-calendar>.

June 2026
WMoTuWeThFrSaSu 23 24 25 26 27 28
June 2026
WMoTuWeThFrSaSu 23 24 25 26 27 28
html<div style="display:flex;gap:16px;flex-wrap:wrap;align-items:flex-start"> <div class="nk-pop"> <div class="nk-calendar weeks"> <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> <div class="cal-grid"> <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> <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> <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> <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> <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> <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> <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> </div> </div> </div> <div class="nk-pop"> <div class="nk-calendar weeks"> <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> <div class="cal-grid"> <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> <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> <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> <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> <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> <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> <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> </div> <div class="cal-foot"><input class="nk-input" type="time" value="09:30" aria-label="Time"><button class="cal-nav">Clear</button></div> </div> </div> </div>
On a small screen:In a sheet the cells grow to 44px, a thumb's width; seven and the week column still fit a 390px screen.
.nk-calendar.weeks.cal-head.cal-title.cal-nav.cal-grid.cal-wd.cal-week.cal-day.out.off.today.start.end.in-range.cal-marks.cal-foot

Buttons .nk-btn

Five variants, 28px tall at 14px. .secondary and .danger are Notion's white button: the outline is --nk-shadow-btn, a 1px inset ring plus a 1px drop, not a border. Hover is an opacity shift on the filled ones and a wash on the white ones — never a hue change. .small (24px) combines with any variant. Stretched across a column – a full-width button on a sign-in page – it keeps its label centred.

html<div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center"> <button class="nk-btn primary">Save changes</button> <button class="nk-btn secondary">Discard</button> <button class="nk-btn danger">Delete</button> <button class="nk-btn danger-solid">Delete</button> <button class="nk-btn secondary small">Remove</button> </div>
On a small screen:Height lands at 28px, under the 44px touch target. Raise the padding for touch-first screens.
.nk-btn.primary.secondary.danger.danger-solid.small

Switch .nk-switch

Works two ways: as an <input type="checkbox"> via :checked, or as a <button role="switch"> via aria-checked="true". The button form is the accessible default. In a settings row the nk-field label names it; for several switches side by side wrap each in label.nk-switch-label with visible text – the text is part of the hit area.

Compact view
Reduces spacing in the sidebar and lists.
Reduce motion
html<div style="max-width:420px"> <div class="nk-field"><div><div class="f-label">Compact view</div><div class="f-desc">Reduces spacing in the sidebar and lists.</div></div> <div class="f-control"><button class="nk-switch" role="switch" aria-checked="true"></button></div></div> <div class="nk-field"><div><div class="f-label">Reduce motion</div></div> <div class="f-control"><button class="nk-switch" role="switch" aria-checked="false"></button></div></div> <div style="display:flex;gap:20px;flex-wrap:wrap;padding:9px 0"> <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> </div>
On a small screen:34×20px, so give it a larger tap area by making the whole nk-field row clickable.
.nk-switch.nk-switch-label.aria-checked

Checkbox & radio .nk-check

The same 16px box for both; the radio variant is detected by [type="radio"] and becomes a circle with a dot. Marks are ::after content, not images; a checkbox that is indeterminate – a property only, set by script – shows a dash, for a group partly chosen. A label that wraps keeps the box beside its first line, as Notion does.

html<div style="display:flex;gap:32px;flex-wrap:wrap"> <div> <label class="nk-check"><input type="checkbox">All</label><script>document.currentScript.previousElementSibling.querySelector('input').indeterminate = true</script> <label class="nk-check"><input type="checkbox" checked>Light</label> <label class="nk-check"><input type="checkbox">Dark</label> </div> <div> <label class="nk-check"><input type="radio" name="nkdemo" checked>Light</label> <label class="nk-check"><input type="radio" name="nkdemo">System</label> </div> <div style="max-width:260px"> <label class="nk-check"><input type="checkbox" checked>Product news and invitations to beta programmes, at most once a month – unsubscribe with one click.</label> </div> </div>
On a small screen:The label wraps the input, so the whole row is the tap target.
.nk-check

Slider .nk-slider

A native range input tinted with accent-color: var(--nk-accent) — no custom track markup, so it keeps native keyboard and screen-reader behaviour. nk-slider-value is the readout.

Text size: 100 %
html<div><input class="nk-slider" type="range" min="80" max="140" value="100"> <div class="nk-slider-value">Text size: 100 %</div></div>
On a small screen:Native thumb sizing gives a comfortable touch target on every platform.
.nk-slider.nk-slider-value

Field row .nk-field

The settings-row primitive: label and description on the left, control on the right, pushed apart by justify-content: space-between. Stack these to build a whole settings pane. stacked puts the label above a full-width control – for textareas and long descriptions; compact shrinks the label to 12px tertiary text and drops the row padding. For several short fields side by side use .nk-fields.

Display name
How you appear in the workspace.
Email
About me
html<div style="max-width:460px"> <div class="nk-field"><div><div class="f-label">Display name</div><div class="f-desc">How you appear in the workspace.</div></div> <div class="f-control"><input class="nk-input" value="Ada Lovelace"></div></div> <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> <div class="nk-field stacked"><div><div class="f-label">About me</div></div> <div class="f-control"><textarea class="nk-textarea" rows="2" placeholder="A few words about yourself …"></textarea></div></div> </div>
On a small screen:Below 860px the row wraps: a control that does not fit beside its label moves below it, and no control grows past its column.
.nk-field.f-label.f-desc.f-control.stacked.compact

Field grid .nk-fields

Several short fields in one row: a grid of minmax(150px, 1fr) columns that wraps as the width allows. Every direct .nk-field child becomes stacked and compact by itself – a 12px label above a full-width control – so nothing collides. .fit lets the fields share the row instead – two fields in a 560px dialog are two halves, not two thirds and a gap.

Name
Email
Status
html<div class="nk-fields" style="max-width:520px"> <div class="nk-field"><div><div class="f-label">Name</div></div><div class="f-control"><input class="nk-input" value="Ada Lovelace"></div></div> <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> <div class="nk-field"><div><div class="f-label">Status</div></div><div class="f-control"><select class="nk-select"><option>In progress</option><option>Done</option></select></div></div> </div>
On a small screen:Wraps to one or two columns on its own; no breakpoint needed.
.nk-fields.fit

Profile row & picture .nk-profile-row

A 56px avatar with its actions beside it, in .pr-actions – upload, change, and the quiet .pr-remove. The gradient matches every other avatar in the system because they all read the same two decor tokens; an <img> inside .big-avatar is a chosen picture, cropped to the circle. square makes it the rounded square of a workspace icon. Choosing the file – a hidden <input type="file"> opened from the button, EXIF rotation, scaling – is your script, or <nk-image-picker>.

AL
html<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> <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>
On a small screen:Unchanged; the actions stay beside the avatar.
.nk-profile-row.big-avatar.square.pr-actions.pr-remove

Model card .nk-model-card

A radio group rendered as cards. The selected card mixes 6 % of the accent into its background and gets an accent border — both derived, so a re-brand carries them.

Mona StandardRecommended
A balanced model for everyday work. Fast, calm, dependable.
Mona Deep
A balanced model for everyday work. Fast, calm, dependable.
html<div style="max-width:460px"> <div class="nk-model-card selected"><div class="m-radio"></div><div> <div class="m-name">Mona Standard<span class="nk-tag green">Recommended</span></div> <div class="m-desc">A balanced model for everyday work. Fast, calm, dependable.</div></div></div> <div class="nk-model-card"><div class="m-radio"></div><div> <div class="m-name">Mona Deep</div><div class="m-desc">A balanced model for everyday work. Fast, calm, dependable.</div></div></div> </div>
On a small screen:Full width by nature; the description wraps under the name.
.nk-model-card.selected.m-radio.m-name.m-desc

Danger zone .nk-danger-zone

Border and title read --nk-danger; the border is 40 % of it via color-mix(), so there is no second red to keep in sync.

⚠️ Danger zone
Delete
Irreversibly removes all pages, databases and members.
html<div class="nk-danger-zone" style="margin-top:0;max-width:460px"> <div class="dz-title">⚠️ Danger zone</div> <div class="nk-field" style="padding-top:0"> <div><div class="f-label">Delete</div><div class="f-desc">Irreversibly removes all pages, databases and members.</div></div> <div class="f-control"><button class="nk-btn danger-solid small">Delete</button></div> </div> </div>
On a small screen:Unchanged.
.nk-danger-zone.dz-title

Member rows .nk-member-row

Rows separated by a hairline. The “no border on the last row” rule is scoped to the nk-member-list container and has a ::slotted() twin, so it keeps working when a future element projects the rows.

AL
Ada Lovelace
ada@acme.com
TW
Tom Weber
tom@acme.com
html<div class="nk-member-list" style="max-width:460px"> <div class="nk-member-row"><span class="nk-avatar purple">AL</span> <div><div>Ada Lovelace</div><div class="m-mail">ada@acme.com</div></div> <select class="nk-select"><option>Admin</option><option>Member</option></select></div> <div class="nk-member-row"><span class="nk-avatar blue">TW</span> <div><div>Tom Weber</div><div class="m-mail">tom@acme.com</div></div> <select class="nk-select"><option>Member</option></select></div> </div>
On a small screen:The name column shrinks first: a long address ends in an ellipsis and the role select keeps its place.
.nk-member-list.nk-member-row.m-mail

Overlays & menus

PRD 5.8

Popover base .nk-pop

The shared surface under every floating panel: page background, 10px radius, the three-layer menu shadow. Positioning is the consumer’s job — the class only supplies the surface.

Actions
✏️Rename
html<div class="nk-pop" style="width:240px"> <div class="nk-menu-label">Actions</div> <div class="nk-menu-item"><span class="m-icon">✏️</span>Rename</div> </div>
On a small screen:Fixed 296px width. On a phone, either widen it or anchor it to the viewport edge.
.nk-pop

Emoji picker .nk-emoji-grid

An eight-column grid inside nk-pop, with a filter field above and a greyed-out category strip below. Categories light up on hover or with .active.

🚀📊💡✅🎨📚🗂️🔍📥⚙️🧩🌙☀️📅👤💬
🕐😀🐶🍎⚽🚗💡
html<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></div> </div>
On a small screen:The grid is fluid; only the 296px popover width is fixed, so widen the popover rather than the grid.
.nk-emoji-search.nk-emoji-grid.nk-emoji-cats

Context menu .nk-menu

Combine nk-pop with nk-menu. Items take an .m-icon on the left and an .m-shortcut pushed right; .danger turns an item red. A .nk-switch as the last child of an item sits on the right, like “Small text” and “Full width” in Notion's page menu. A menu that floats over the page takes floating and opens and closes with .open, the way the palette does: it fades in and settles from 4px higher and 98 %; closed it takes no clicks and no focus, and where it sits is yours. sheet makes it a bottom sheet on a phone, as Notion's mobile app opens every menu – the same markup, two presentations. A floating menu never grows past the window: a longer one scrolls inside, and --_nk-float-max caps it to the room a script found below or above its anchor. It lies above the modal, the sheet and the dialog, so a menu or a date picker opened from one of them shows over it.

Pages
✏️Rename⌘⇧R
📄Duplicate⌘D
🔗Copy link⌘L
🔡Small text
🗑Move to trash
html<div class="nk-pop nk-menu floating sheet open" style="position:fixed; top:48px; right:12px">…</div>
html<div class="nk-pop nk-menu"> <div class="nk-menu-label">Pages</div> <div class="nk-menu-item"><span class="m-icon">✏️</span>Rename<span class="m-shortcut">⌘⇧R</span></div> <div class="nk-menu-item"><span class="m-icon">📄</span>Duplicate<span class="m-shortcut">⌘D</span></div> <div class="nk-menu-item"><span class="m-icon">🔗</span>Copy link<span class="m-shortcut">⌘L</span></div> <div class="nk-menu-sep"></div> <div class="nk-menu-item"><span class="m-icon">🔡</span>Small text<button class="nk-switch" role="switch" aria-checked="true" aria-label="Small text"></button></div> <div class="nk-menu-sep"></div> <div class="nk-menu-item danger"><span class="m-icon">🗑</span>Move to trash</div> </div> <pre class="nk-code" style="margin-top:12px"><span class="lang">html</span>&lt;div class="nk-pop nk-menu floating sheet open" style="position:fixed; top:48px; right:12px"&gt;…&lt;/div&gt;</pre>
On a small screen:Shortcuts are meaningless on touch — hide the .m-shortcut spans there. With sheet the menu becomes a bottom sheet below 860px: full width, a grabber, 40px rows, the page dimmed behind it; inline positioning is overruled.
.nk-menu.nk-menu-item.m-icon.m-shortcut.danger.nk-menu-sep.nk-menu-label.floating.sheet.open

Command palette .nk-cmdk

A ⌘K palette: input row, grouped list, footer with key hints. The keyboard-highlighted row carries .selected — that is the class your arrow-key handler moves around.

🔍
Pages
📊Project overview
📚Knowledge base
Actions
+New page⌘N
⚙️Open settings⌘,
html<div class="nk-cmdk-backdrop open" style="position:absolute;padding-top:22px;border-radius:var(--nk-radius)"> <div class="nk-cmdk" style="width:min(460px, 92%)"> <div class="nk-cmdk-input-row"><span>🔍</span><input placeholder="Search or type a command …"></div> <div class="nk-cmdk-list"> <div class="nk-cmdk-group">Pages</div> <div class="nk-cmdk-item selected"><span class="m-icon">📊</span>Project overview</div> <div class="nk-cmdk-item"><span class="m-icon">📚</span>Knowledge base</div> <div class="nk-cmdk-group">Actions</div> <div class="nk-cmdk-item"><span class="m-icon">+</span>New page<span class="m-shortcut">⌘N</span></div> <div class="nk-cmdk-item"><span class="m-icon">⚙️</span>Open settings<span class="m-shortcut">⌘,</span></div> </div> <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> </div> </div>
On a small screen:The backdrop’s padding-top drops from 14vh to 6vh and the palette widens to min(560px, 96vw), so it fills a phone screen instead of floating in the middle.
.nk-cmdk-backdrop.nk-cmdk.nk-cmdk-input-row.nk-cmdk-list.nk-cmdk-group.nk-cmdk-item.selected.nk-cmdk-empty.nk-cmdk-footer

Palette: no results .nk-cmdk-empty

What the list shows when the filter matches nothing. Quote the query back so the person can see what was searched for.

🔍
No results for “xyzzy”
html<div class="nk-cmdk" style="box-shadow:none;border:1px solid var(--nk-border);width:min(460px,100%)"> <div class="nk-cmdk-input-row"><span>🔍</span><input value="xyzzy" placeholder="Search or type a command …"></div> <div class="nk-cmdk-list"><div class="nk-cmdk-empty">No results for “xyzzy”</div></div> </div>
On a small screen:Same as the palette: full width, reduced top padding.
.nk-cmdk-empty

Sheet .nk-sheet

Notion's mobile surface for menus, properties and more: the phone's twin of the modal. The backdrop fades, the panel rises from the bottom edge with its top corners rounded and a .sh-grabber on top, and the home indicator keeps its distance. It lies above the tab bar. Like the modal it starts invisible and .open shows it; closed, the backdrop leaves the layout once it has faded. Up to 640px wide, centred on larger screens. Rows inside get 40px, a thumb's height. For a menu that is a popover on the desktop and a sheet on the phone, use .nk-pop.sheet instead.

html<div class="nk-sheet-backdrop open" style="position:absolute;border-radius:var(--nk-radius)"> <div class="nk-sheet" role="dialog" aria-modal="true" aria-label="Page"> <div class="sh-grabber"></div> <div class="sh-title">Page</div> <div class="nk-menu-item"><span class="m-icon">🔡</span>Small text<button class="nk-switch" role="switch" aria-checked="true" aria-label="Small text"></button></div> <div class="nk-menu-item"><span class="m-icon">🔗</span>Copy link</div> <div class="nk-menu-item"><span class="m-icon">📄</span>Duplicate</div> <div class="nk-menu-item danger"><span class="m-icon">🗑</span>Move to trash</div> </div> </div>
On a small screen:Made for the phone: full width, bottom padding from the safe area, the content scrolls inside the sheet and never moves the page.
.nk-sheet-backdrop.open.nk-sheet.sh-grabber.sh-title

Side peek .nk-peek

Notion's side peek: a database row opens in a panel at the right edge, full height, next to the table, which stays visible and usable – there is no scrim, the backdrop only holds the panel and lets clicks through. .pk-bar carries the actions – close with », open as page – and .pk-body the page: title in 32px, properties, prose, comments. .open on the backdrop slides the panel in; closed, it leaves the layout once it has slid out. It lies above the page and the tab bar and below the floating menus, so a menu opened in the peek shows over it. Closing is your script: », Escape, a click outside; a click on another row swaps the content. Its width is the token --nk-peek-width (560px): .pk-resize on the left edge takes the pointer to make it wider or narrower, as in Notion – set the token on :root while dragging; it shows a line while hovered, focused or .active. .peek-inset on .nk-app gives the main column a right padding as wide as the peek, so the page moves aside instead of lying under it – for a Gantt chart whose bars must stay visible.

jsrow.addEventListener('click', () => peek.classList.add('open'));        // a row opens beside the table
closeBtn.addEventListener('click', () => peek.classList.remove('open'));
document.addEventListener('keydown', e => { if (e.key === 'Escape') peek.classList.remove('open'); });
handle.addEventListener('pointermove', e =>                  // .pk-resize, with pointer capture
  document.documentElement.style.setProperty('--nk-peek-width', innerWidth - e.clientX + 'px'));
app.classList.toggle('peek-inset', open);                     // the page moves aside
html<div class="nk-peek-backdrop open" style="position:absolute;border-radius:var(--nk-radius);overflow:hidden"> <aside class="nk-peek" role="dialog" aria-label="Database Table-View" style="width:min(440px, 100%)"> <div class="pk-resize" role="separator" aria-orientation="vertical" aria-label="Resize" tabindex="0"></div> <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> <div class="pk-body"> <h1 class="nk-page-title">🗃️ Database Table-View</h1> <dl class="nk-props"> <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> <div class="nk-prop"><dt class="p-name"><span class="p-icon">📅</span>Due</dt><dd class="p-value">20.05.2026</dd></div> </dl> <div class="nk-prose"><p>Table, board and list read the same rows; filters and sort act on all three.</p></div> </div> </aside> </div>
On a small screen:Below 860px it rises from the bottom edge as a sheet over a dimmed page – full width, a grabber, the title in 28px – and the dimmed page takes the tap that closes it.
.nk-peek-backdrop.open.nk-peek.pk-resize.pk-bar.pk-body.active.peek-inset

Dialog .nk-dialog

A question or a short form – “Move to trash?”, the name of a new view, the link between two tasks – in a card of 440px, .wide 560px: .dl-title, .dl-body and .dl-actions on the right with the confirming button last. The contract of the modal and the sheet: .open on the backdrop fades it in over a scrim and the card settles like the modal's; closed, the backdrop leaves the layout once it has faded. It lies above the modal and the sheet, so a question can come from either. Closing is your script: Escape (captured, before the overlays behind it), the backdrop, the buttons; move focus in and back out. role="alertdialog" for a question, role="dialog" for a form.

Move “Product roadmap” to trash?
The page and its sub-pages can be restored from Trash for 30 days.
html<div class="nk-dialog-backdrop open">
  <div class="nk-dialog wide" role="dialog" aria-modal="true" aria-labelledby="t">
    <div class="dl-title" id="t">New view</div>
    <div class="dl-body">
      <div class="nk-field stacked"><div><div class="f-label">Name</div></div><div class="f-control"><input class="nk-input"></div></div>
    </div>
    <div class="dl-actions"><button class="nk-btn secondary">Cancel</button><button class="nk-btn primary">Create</button></div>
  </div>
</div>
html<div class="nk-dialog-backdrop open" style="position:absolute;border-radius:var(--nk-radius)"> <div class="nk-dialog" role="alertdialog" aria-modal="true" aria-label="Move to trash"> <div class="dl-title">Move “Product roadmap” to trash?</div> <div class="dl-body">The page and its sub-pages can be restored from Trash for 30 days.</div> <div class="dl-actions"><button class="nk-btn secondary">Cancel</button><button class="nk-btn danger-solid">Move to trash</button></div> </div> </div>
On a small screen:Below 860px it rises from the bottom edge as a sheet – full width, a grabber, the safe area kept – and the buttons stack across the width, the confirming one on top.
.nk-dialog-backdrop.open.nk-dialog.wide.dl-title.dl-body.dl-actions

Toast .nk-toast

Fixed to the bottom centre, inverted (text colour as background). It sits off-screen until .show is added, then slides up. pointer-events: none keeps it from stealing clicks. With an action it is Notion's “Moved to trash · Undo”: a .t-action button in the toast's own colours and a .t-close × – the only parts that take the pointer, and only while the toast shows. Keep such a toast until one of them is used; the timing is your script.

✓ Settings saved
🗑️ Moved to trash
html<div class="nk-toast show" style="position:relative;bottom:auto;left:auto;transform:none;display:inline-flex;z-index:auto">✓ <span>Settings saved</span></div> <div class="nk-toast show" style="position:relative;bottom:auto;left:auto;transform:none;display:inline-flex;z-index:auto;margin-top:12px">🗑️ <span>Moved to trash</span><button class="t-action" type="button">Undo</button><button class="t-close" type="button" aria-label="Close"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M18 6 6 18M6 6l12 12"/></svg></button></div>
On a small screen:Centred by translateX(-50%), so it stays centred at any width.
.nk-toast.show.t-action.t-close

Tooltip .nk-tooltip

A hover hint in the toast's colours: 12px, at most 260px wide, a shortcut in .tt-key, muted. Invisible and inert until .open fades it in; where it sits is yours – fixed, top and left, 6px below the button and centred, above it where the window ends. It lies above every overlay and never takes the pointer. Show it after a moment under the pointer and at once on keyboard focus, hide it on leave, a press, Escape; name it in the button's aria-describedby. Long words break; .lines keeps the line breaks of its text, for a hint of two or three lines.

html<div style="position:relative;height:84px"> <button class="nk-topbar-btn" style="position:absolute;left:61px;top:6px" aria-label="Style, export and more">⋯</button> <div class="nk-tooltip open" role="tooltip" style="position:absolute;left:8px;top:40px">Style, export and more</div> <button class="nk-sidebar-collapse" style="position:absolute;left:300px;top:6px;opacity:1" 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 class="nk-tooltip open" role="tooltip" style="position:absolute;left:255px;top:40px">Close sidebar<span class="tt-key">⌘\</span></div> </div>
On a small screen:A phone has no hover: leave tooltips to the pointer and show nothing on touch.
.nk-tooltip.open.tt-key.lines

Collaboration & AI

PRD 5.10

Comment thread .nk-comments

A thread hanging off a left rule, as it would beside a paragraph. Each comment is an avatar plus a head (name and time) and a body.

SL
Sara Lindt · 1 hr ago
The board view already feels very close to the original. 👍
html<div class="nk-comments" style="max-width:420px"> <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 class="nk-comment-input"> <input class="nk-input" placeholder="Comment …"><button class="nk-btn primary small">Send</button> </div> </div>
On a small screen:The 18px indent stays; place the thread below the paragraph rather than beside it on narrow screens.
.nk-comments.nk-comment.c-head.c-body.nk-comment-input

AI thread .nk-ai-thread

An assistant conversation as part of the document, not a floating widget. .user gives the message the gradient avatar; the assistant keeps the neutral callout circle. bubble sets a message as a grey bubble without avatar or name – with .user on the right, the way Notion's AI chat shows your own question. The input row glows on :focus-within.

AL
You
Summarise the open tasks for this project.
What is still open on this page?
🤖
Mona · AI
Two tasks are open: the table view sits at 65 %, the board with drag and drop is planned.
html<div style="max-width:460px"> <div class="nk-ai-thread"> <div class="nk-ai-msg user"> <span class="mini-avatar">AL</span> <div><div class="a-name">You</div><div class="a-body">Summarise the open tasks for this project.</div></div> </div> <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><div class="a-name">Mona <span>· AI</span></div> <div class="a-body">Two tasks are open: the table view sits at 65 %, the board with drag and drop is planned.</div> <div class="nk-ai-actions"><button>📋 Copy</button><button>↻ Rephrase</button></div></div> </div> </div> <div class="nk-ai-input-row"> <input placeholder="Ask Mona something …"><button class="nk-ai-send">↑</button> </div> </div>
On a small screen:Flows naturally. The input row is a flex line with a fixed 26px send button.
.nk-ai-thread.nk-ai-msg.user.bubble.a-name.a-body.nk-ai-actions.nk-ai-input-row.nk-ai-send

Editor adapter

PRD 5.11

Block host .nk-block-host

The optical shell an editor is mounted into. It supplies the hover wash, the focus ring on :focus-within, a slot for a drag handle to the left of the column, and .nk-drop-target for drag feedback. It is behaviour-free by design.

Core idea: a callout carries one thought that should not be missed. Keep it to a sentence or two.
⠿
Type “/” for commands …

This preview is the static shell only — the + / ⠿ rail here does nothing. The working version, with slash menu, drag & drop and block menu, is the live editor in the docs.

html<div style="padding-left:48px;max-width:460px"> <div class="nk-block-host"> <div class="nk-block-actions show" style="top:6px;left:-46px;pointer-events:none" aria-hidden="true"><button type="button" tabindex="-1">+</button><button type="button" class="drag" tabindex="-1">⠿</button></div> <div style="padding:4px 2px;line-height:1.6">Core idea: a callout carries one thought that should not be missed. Keep it to a sentence or two.</div> </div> <div class="nk-block-host nk-drop-target"> <span class="nk-block-handle">⠿</span> <div style="padding:4px 2px;color:var(--nk-text-tertiary)">Type “/” for commands …</div> </div> </div>
On a small screen:The handle sits at left: -26px, outside the column. On narrow screens hide it and use a long-press menu.
.nk-block-host.nk-block-handle.nk-block-actions.nk-drop-target

Slash menu & bubble toolbar .nk-slash-menu

Give your editor’s floating containers these classes and they inherit the NotionKit popover look. The same rules also target .bn-suggestion-menu and .tippy-box inside a block host, so TipTap and BlockNote need no extra markup.

Basic blocks
H1
Heading 1
Big section heading
☑
To-do list
Track tasks with a checkbox
</>
Code block
Capture a snippet

Static markup for the look. The live, keyboard-driven version runs in the docs editor — type / there.

html<div style="display:flex;gap:20px;flex-wrap:wrap;align-items:flex-start"> <div class="nk-slash-menu" style="box-shadow:none;border:1px solid var(--nk-border)"> <div class="nk-slash-menu-label">Basic blocks</div> <div class="nk-slash-item selected"><span class="m-icon">H1</span><div><div>Heading 1</div><div class="m-desc">Big section heading</div></div></div> <div class="nk-slash-item"><span class="m-icon">☑</span><div><div>To-do list</div><div class="m-desc">Track tasks with a checkbox</div></div></div> <div class="nk-slash-item"><span class="m-icon">&lt;/&gt;</span><div><div>Code block</div><div class="m-desc">Capture a snippet</div></div></div> </div> <div class="nk-bubble-menu" style="box-shadow:none;border:1px solid var(--nk-border)"> <button class="active"><b>B</b></button><button><i>I</i></button><button><s>S</s></button><button>&lt;/&gt;</button><button>🔗</button> </div> </div>
On a small screen:Fixed 280px width. Anchor it to the viewport edge on a phone rather than to the caret.
.nk-slash-menu.nk-slash-menu-label.nk-slash-item.selected.nk-bubble-menu