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 -->
<linkrel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit@1/notionkit.min.css">
<!-- local -->
<linkrel="stylesheet" href="notionkit.css">
<!-- your own theme, after the library -->
<linkrel="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:
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.
Token
Light
Dark
What it is
--nk-bg
#ffffff
#191919
Page background
--nk-bg-sidebar
#f7f6f3
#202020
Sidebar and settings nav
--nk-bg-hover
rgba(0,0,0,0.045)
rgba(255,255,255,0.055)
Hover wash — a darkening, never a hue change
--nk-bg-active
rgba(0,0,0,0.06)
rgba(255,255,255,0.08)
Active/selected row
--nk-bg-callout
#f1f1ef
#252525
Callouts, badges, key caps, segmented track
--nk-bg-code
#f7f6f3
#202020
Code block background
--nk-bg-card
#ffffff
#202020
Board cards — separate from --nk-bg so dark mode can lift them
--nk-text
#37352f
rgba(255,255,255,0.81)
Body text. Never pure black
--nk-text-secondary
rgba(55,53,47,0.65)
rgba(255,255,255,0.46)
Secondary text, inactive tree items
--nk-text-tertiary
rgba(55,53,47,0.45)
rgba(255,255,255,0.28)
Meta, placeholders, captions
--nk-border
rgba(55,53,47,0.09)
rgba(255,255,255,0.094)
Hairline separators
--nk-border-strong
rgba(55,53,47,0.16)
rgba(255,255,255,0.16)
Input borders, scrollbar thumbs, dashed frames
--nk-accent
#2383e2
#529CCA
The 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
#eb5757
—
Destructive actions, inline code, negative deltas
--nk-tag-blue-bg
#e7f3f8
#133040
Tag background, also the progress track
--nk-tag-blue-text
#337ea9
#529CCA
Tag text, also the progress fill
--nk-tag-green-bg
#edf3ec
#173B2C
Tag background
--nk-tag-green-text
#448361
#4DAB9A
Tag text, also the positive delta
--nk-tag-orange-bg
#fbecdd
#40291B
Tag background
--nk-tag-orange-text
#d9730d
#FFA344
Tag text, also date mentions and code attributes
--nk-tag-purple-bg
#f6f3f9
#2E2440
Tag background
--nk-tag-purple-text
#9065b0
#9A6DD7
Tag text
--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-scrim
rgba(15,15,15,0.6)
—
Modal backdrop
--nk-scrim-soft
rgba(15,15,15,0.5)
—
Command palette backdrop, one step lighter
--nk-sidebar-width
260px
—
Sidebar width, also its min-width
--nk-radius
6px
—
Control radius. Cards and modals use 8–12px directly
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<htmldata-theme="light"> <!-- default, the attribute may be omitted -->
<htmldata-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.
Marks the current item. Tree items get the active background, tabs get the underline, panes become visible.
.open
nk-modal-backdropnk-cmdk-backdropnk-toggle-arrow
Fades the overlay in and makes it interactive; rotates the toggle arrow by 90°.
.collapsed
nk-tree-children
Folds a subtree away with display: none.
.selected
nk-cmdk-itemnk-model-cardnk-slash-item
The keyboard-highlighted or chosen option. Distinct from active: selection is transient, active is where you are.
.show
nk-toast
Slides 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.
.:checked
nk-todo inputnk-check inputnk-switch (input form)
Native state. Draws the custom checkmark or dot and strikes a to-do label through.
.nk-drop-target
nk-block-host
Drag 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.
Pair
Light
Dark
AA body text
--nk-text / --nk-bg
12.26
11.78
✓
--nk-text / --nk-bg-sidebar
11.35
11.06
✓
--nk-text-secondary / --nk-bg
4.19
4.63
✗
--nk-text-tertiary / --nk-bg
2.49
2.53
—
--nk-accent / --nk-bg
3.88
5.83
—
--nk-on-accent / --nk-accent
3.88
3.01
✗
--nk-danger / --nk-bg
3.48
5.05
✗
tag blue
3.95
4.57
—
tag green
3.99
4.48
—
tag orange
2.83
6.82
—
tag purple
4.09
3.84
—
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<divclass="nk-block-host" id="editor"></div><scripttype="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.
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.
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 — 57 of them. 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.
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).
📊 Project overview
Product roadmap
A short lead paragraph sets up the page in a calmer, larger type size before the body copy begins.
html<divclass="nk-app" style="height:100%"><asideclass="nk-sidebar"><divclass="nk-workspace"><divclass="avatar">A</div>Acme Inc<spanclass="chev">⌄</span></div><divclass="nk-sidebar-scroll"><divclass="nk-tree-item"><spanclass="icon">🔍</span><spanclass="label">Search</span></div><divclass="nk-tree-item active"><spanclass="icon">🏠</span><spanclass="label">Home</span></div><divclass="nk-tree-item"><spanclass="icon">📥</span><spanclass="label">Inbox</span></div></div><divclass="nk-sidebar-footer"><divclass="nk-tree-item"><spanclass="icon">⚙️</span><spanclass="label">Settings</span></div></div></aside><mainclass="nk-main"><headerclass="nk-topbar"><divclass="nk-breadcrumb"><spanclass="crumb current">📊 Project overview</span></div><divclass="nk-topbar-actions"><buttonclass="nk-topbar-btn nk-share-btn">Share</button></div></header><divclass="nk-page-scroll"><divclass="nk-page" style="padding-top:16px"><h1class="nk-page-title" style="font-size:28px">Product roadmap</h1><pclass="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 is hidden entirely and the main column takes the full width. An off-canvas drawer is the consumer’s job (and a planned Elements component).
On a small screen:Hidden together with the sidebar below 860px.
.nk-workspace.avatar.chev
Topbar .nk-topbar
A 45px-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.
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.
On a small screen:Hidden inside the settings nav below 860px, where the nav collapses to icons.
.nk-section-label.plus
Navigation / page tree
PRD 5.2
Tree item .nk-tree-item
The workhorse of the sidebar. Minimum height is 28px, the label truncates with an ellipsis, and the .actions block stays hidden until hover. Add .active for the current page.
On a small screen:Reaches 28px, below the 44px touch target. In a touch-first off-canvas drawer raise min-height on the item; the class does not force a height.
.nk-tree-item.icon.label.actions.active
Nested tree & toggle arrow .nk-tree-children
Children indent under a guide line. Add .collapsed to fold them away and .open to the arrow to rotate it 90°. Both are plain state classes — the toggling is yours.
On a small screen:Unchanged; the indent stays at 14px so deep trees still fit a narrow rail.
.nk-tree-children.collapsed.nk-toggle-arrow.open
Keyboard hint .nk-kbd
nk-kbd-hint pushes a shortcut to the right edge of a row; nk-kbd is the key cap itself. Prefixed on purpose — a bare kbd rule would leak into the host page.
On a small screen:Keep it, but do not rely on it: touch devices have no such shortcut.
.nk-kbd-hint.nk-kbd
Page shell & document
PRD 5.3
Page column .nk-page
The document column: max-width: 760px with auto margins, never a fixed width. The icon pulls itself up over the cover with a negative margin; the title is contenteditable-ready.
🚀
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<divclass="nk-page-scroll" style="height:100%"><divclass="nk-cover" style="height:120px"></div><divclass="nk-page" style="padding-bottom:24px"><divclass="nk-page-icon">🚀</div><h1class="nk-page-title" contenteditable="true">Product roadmap</h1><divclass="nk-page-meta"><span>👤 Ada Lovelace</span><span>📅 Created 12 May 2026</span></div><pclass="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.
A 200px decorative band above the page. Three radial gradients mixed from the --nk-decor-* tokens over --nk-bg-callout. Swap the whole background for a photo.
On a small screen:Fixed 200px height, full bleed. Reduce it yourself if it eats too much of a short screen.
.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<divclass="nk-page" style="padding:0;max-width:none"><h2class="nk-heading">✅ How this works</h2><pclass="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<divclass="nk-callout"><spanclass="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
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><labelclass="nk-todo"><inputtype="checkbox" checked><span>Sketch the component scope</span></label><labelclass="nk-todo"><inputtype="checkbox" checked><span>Build the preview</span></label><labelclass="nk-todo"><inputtype="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<detailsclass="nk-toggle" open><summary>What is not included?</summary><divclass="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<blockquoteclass="nk-quote">Design is not just what it looks like. Design is how it works.<citeclass="q-cite">— Steve Jobs</cite></blockquote><hrclass="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.
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.
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
Table view .nk-table
Header cells are quiet and clickable, rows highlight on hover, and every cell is white-space: nowrap so columns keep their shape. .new-row is the add affordance at the bottom.
One shared shape for all three. The focus ring is color-mix(in srgb, var(--nk-accent) 25%, transparent), so it re-brands with the accent. .wide makes an input fill its row.
html<divstyle="display:flex;flex-direction:column;gap:10px;max-width:340px"><inputclass="nk-input wide" value="Ada Lovelace"><selectclass="nk-select wide"><option>Light</option><option>Dark</option><option>System</option></select><textareaclass="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
Buttons .nk-btn
Five variants. Hover is an opacity shift on the filled ones and a background wash on the outlined ones — never a hue change. .small combines with any variant.
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.
Compact view
Reduces spacing in the sidebar and lists.
Reduce motion
html<divstyle="max-width:420px"><divclass="nk-field"><div><divclass="f-label">Compact view</div><divclass="f-desc">Reduces spacing in the sidebar and lists.</div></div><divclass="f-control"><buttonclass="nk-switch" role="switch" aria-checked="true"></button></div></div><divclass="nk-field"><div><divclass="f-label">Reduce motion</div></div><divclass="f-control"><buttonclass="nk-switch" role="switch" aria-checked="false"></button></div></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.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.
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.
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.
Display name
How you appear in the workspace.
Email
html<divstyle="max-width:460px"><divclass="nk-field"><div><divclass="f-label">Display name</div><divclass="f-desc">How you appear in the workspace.</div></div><divclass="f-control"><inputclass="nk-input" value="Ada Lovelace"></div></div><divclass="nk-field"><div><divclass="f-label">Email</div></div><divclass="f-control"><inputclass="nk-input" value="ada@acme.com"></div></div></div>
On a small screen:The 24px gap keeps both sides apart; add flex-wrap: wrap yourself if the control needs its own line.
.nk-field.f-label.f-desc.f-control
Profile row .nk-profile-row
A 56px avatar with the two actions beside it. The gradient matches every other avatar in the system because they all read the same two decor tokens.
On a small screen:Unchanged; the buttons wrap under the avatar on very narrow screens.
.nk-profile-row.big-avatar
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<divstyle="max-width:460px"><divclass="nk-model-card selected"><divclass="m-radio"></div><div><divclass="m-name">Mona Standard<spanclass="nk-tag green">Recommended</span></div><divclass="m-desc">A balanced model for everyday work. Fast, calm, dependable.</div></div></div><divclass="nk-model-card"><divclass="m-radio"></div><div><divclass="m-name">Mona Deep</div><divclass="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<divclass="nk-danger-zone" style="margin-top:0;max-width:460px"><divclass="dz-title">⚠️ Danger zone</div><divclass="nk-field" style="padding-top:0"><div><divclass="f-label">Delete</div><divclass="f-desc">Irreversibly removes all pages, databases and members.</div></div><divclass="f-control"><buttonclass="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.
On a small screen:The role select is pushed right by margin-left:auto; on narrow rows let it wrap.
.nk-member-list.nk-member-row.m-mail
Settings modal
PRD 5.7
Settings modal .nk-modal
A two-column overlay: nav left, panes right. The backdrop starts at opacity: 0; pointer-events: none; adding .open fades it in and scales the modal from 0.98 to 1. Panes switch with .active. The modal sizes itself – min(960px, 92vw) by min(640px, 86vh) – so it needs no inline dimensions.
Display name
Display name
How you appear in the workspace.
Compact view
html<divclass="nk-modal-backdrop open" style="position:absolute;border-radius:var(--nk-radius)"><divclass="nk-modal" style="width:96%;height:92%"><navclass="nk-settings-nav"><divclass="nk-settings-user"><divclass="avatar">AL</div><divclass="u-text"><divclass="name">Ada Lovelace</div><divclass="mail">ada@acme.com</div></div></div><divclass="nk-section-label">Settings</div><divclass="nk-tree-item active"><spanclass="icon">👤</span><spanclass="label">Display name</span></div><divclass="nk-tree-item"><spanclass="icon">🎨</span><spanclass="label">Light/Dark</span></div><divclass="nk-tree-item"><spanclass="icon">🤖</span><spanclass="label">Mona</span></div></nav><divclass="nk-settings-content" style="padding:24px 28px"><divclass="nk-settings-pane active"><h2>Display name</h2><divclass="nk-field"><div><divclass="f-label">Display name</div><divclass="f-desc">How you appear in the workspace.</div></div><divclass="f-control"><inputclass="nk-input" value="Ada Lovelace"></div></div><divclass="nk-field"><div><divclass="f-label">Compact view</div></div><divclass="f-control"><buttonclass="nk-switch" role="switch" aria-checked="true"></button></div></div></div></div></div></div>
On a small screen:Below 860px the nav shrinks to a 60px icon rail — labels, section labels and the user’s name/mail block (.u-text) are hidden, and content padding drops to 24px.
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.
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<divclass="nk-cmdk-backdrop open" style="position:absolute;padding-top:22px;border-radius:var(--nk-radius)"><divclass="nk-cmdk" style="width:min(460px, 92%)"><divclass="nk-cmdk-input-row"><span>🔍</span><inputplaceholder="Search or type a command …"></div><divclass="nk-cmdk-list"><divclass="nk-cmdk-group">Pages</div><divclass="nk-cmdk-item selected"><spanclass="m-icon">📊</span>Project overview</div><divclass="nk-cmdk-item"><spanclass="m-icon">📚</span>Knowledge base</div><divclass="nk-cmdk-group">Actions</div><divclass="nk-cmdk-item"><spanclass="m-icon">+</span>New page<spanclass="m-shortcut">⌘N</span></div><divclass="nk-cmdk-item"><spanclass="m-icon">⚙️</span>Open settings<spanclass="m-shortcut">⌘,</span></div></div><divclass="nk-cmdk-footer"><span><kbdclass="nk-kbd">↑</kbd><kbdclass="nk-kbd">↓</kbd> navigate</span><span><kbdclass="nk-kbd">↵</kbd> open</span><span><kbdclass="nk-kbd">⌘</kbd><kbdclass="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.
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<divclass="nk-cmdk" style="box-shadow:none;border:1px solid var(--nk-border);width:min(460px,100%)"><divclass="nk-cmdk-input-row"><span>🔍</span><inputvalue="xyzzy" placeholder="Search or type a command …"></div><divclass="nk-cmdk-list"><divclass="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
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.
On a small screen:Centred by translateX(-50%), so it stays centred at any width.
.nk-toast.show
Gallery & productivity
PRD 5.9
Gallery grid .nk-gallery-grid
A fluid repeat(auto-fit, minmax(min(280px, 100%), 1fr)) grid. There is no breakpoint here on purpose: the number of columns follows the container, not the viewport.
On a small screen:Falls to one column as soon as the container drops under ~600px — no media query involved.
.nk-gallery-grid.nk-g-item
Tabs .nk-tabs
In-page tabs, visually the quieter sibling of the database view tabs. Show and hide the panels yourself; the library only styles them.
📝 Notes
✅ Tasks
📎 Files
Panel content follows the tab strip and inherits the calm body type.
html<div><divclass="nk-tabs"><divclass="nk-tab active">📝 Notes</div><divclass="nk-tab">✅ Tasks</div><divclass="nk-tab">📎 Files</div></div><divclass="nk-tab-panel">Panel content follows the tab strip and inherits the calm body type.</div></div>
On a small screen:Add overflow-x: auto to nk-tabs when the strip gets long.
.nk-tabs.nk-tab.active.nk-tab-panel
Template button .nk-template-btn
A full-width, left-aligned button on the callout background — the “insert a prepared block” affordance inside a document.
On a small screen:Full width by default, so nothing to adjust.
.nk-template-btn
Stat cards .nk-stats
Equal-width cards in a flex row. .s-delta.up takes the green tag colour, .down takes --nk-danger — so the direction is a class, never an inline style.
Active pages
128
▲ 12 this week
AI requests
1 204
▲ 8 %
Open tasks
17
▼ 5 since yesterday
html<divclass="nk-stats"><divclass="nk-stat"><divclass="s-label">Active pages</div><divclass="s-value">128</div><divclass="s-delta up">▲ 12 this week</div></div><divclass="nk-stat"><divclass="s-label">AI requests</div><divclass="s-value">1 204</div><divclass="s-delta up">▲ 8 %</div></div><divclass="nk-stat"><divclass="s-label">Open tasks</div><divclass="s-value">17</div><divclass="s-delta down">▼ 5 since yesterday</div></div></div>
On a small screen:The row does not wrap on its own; the 860px breakpoint adds flex-wrap: wrap so cards stack.
.nk-stats.nk-stat.s-label.s-value.s-delta.up.down
Synced block .nk-synced
Content mirrored across several pages, marked by a danger-coloured outline and a badge notched into the top edge. The border is 55 % of --nk-danger.
⟳ 3 places
This text appears identically on three pages and is maintained in one place.
html<divclass="nk-synced" style="max-width:420px"><spanclass="synced-badge">⟳ 3 places</span>
This text appears identically on three pages and is maintained in one place.
</div>
On a small screen:The badge is absolutely positioned at the top-right and stays there at any width.
.nk-synced.synced-badge
Segmented control .nk-segmented
A small set of mutually exclusive options. The active segment lifts out of the track with the page background and a one-pixel shadow.
On a small screen:inline-flex, so it shrinks to its content; keep it to three or four segments.
.nk-segmented.active
Banner .nk-banner
A full-width notice in three semantic tones, each reusing a tag colour pair. .b-action pushes an underlined action to the right edge.
ℹ️ The “Project overview” database has 2 overdue entries.View
✅ All changes have been synced.
⚠️ Your trial ends in 5 days.View
html<divclass="nk-banner info">ℹ️ The “Project overview” database has 2 overdue entries.<spanclass="b-action">View</span></div><divclass="nk-banner success">✅ All changes have been synced.</div><divclass="nk-banner warning">⚠️ Your trial ends in 5 days.<spanclass="b-action">View</span></div>
On a small screen:The action stays on the same line; wrap the banner content yourself if it gets crowded.
.nk-banner.info.success.warning.b-action
Avatar group .nk-avatar-group
Overlapping avatars with a page-coloured ring, so they read as a stack. The :first-child reset lives inside the group and has a ::slotted() twin.
ALTWSL+2
5 people have access
html<divstyle="display:flex;align-items:center;gap:10px"><divclass="nk-avatar-group"><spanclass="mini-avatar" style="background:var(--nk-decor-purple)">AL</span><spanclass="mini-avatar" style="background:var(--nk-decor-blue)">TW</span><spanclass="mini-avatar" style="background:var(--nk-tag-green-text)">SL</span><spanclass="mini-avatar more">+2</span></div><spanstyle="font-size:13px;color:var(--nk-text-tertiary)">5 people have access</span></div>
On a small screen:Unchanged. Cap the count and let .more carry the remainder.
.nk-avatar-group.mini-avatar.more
Skeleton .nk-skeleton
A shimmering placeholder. Set width and height yourself. Under prefers-reduced-motion: reduce the animation stops and it falls back to a flat callout-coloured block.
On a small screen:Use percentage widths so the placeholder matches the content it stands in for.
.nk-skeleton
Empty state .nk-empty
A dashed frame with icon, title and one explanatory line. Meant to hold exactly one action — the way out of the empty state.
🗂️
No entries yet
Create the first entry or import existing data.
html<divclass="nk-empty" style="max-width:420px"><divclass="e-icon">🗂️</div><divclass="e-title">No entries yet</div><divclass="e-desc">Create the first entry or import existing data.</div><buttonclass="nk-btn secondary small">+ New entry</button></div>
On a small screen:Centred and fluid; padding drops naturally with the container.
.nk-empty.e-icon.e-title.e-desc
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<divclass="nk-comments" style="max-width:420px"><divclass="nk-comment"><spanclass="mini-avatar" style="background:var(--nk-decor-purple)">SL</span><div><divclass="c-head"><b>Sara Lindt</b> · 1 hr ago</div><divclass="c-body">The board view already feels very close to the original. 👍</div></div></div><divclass="nk-comment-input"><inputclass="nk-input" placeholder="Comment …"><buttonclass="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.
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. The input row glows on :focus-within.
AL
You
Summarise the open tasks for this project.
🤖
Mona · AI
Two tasks are open: the table view sits at 65 %, the board with drag and drop is planned.
html<divstyle="max-width:460px"><divclass="nk-ai-thread"><divclass="nk-ai-msg user"><spanclass="mini-avatar">AL</span><div><divclass="a-name">You</div><divclass="a-body">Summarise the open tasks for this project.</div></div></div><divclass="nk-ai-msg"><spanclass="mini-avatar">🤖</span><div><divclass="a-name">Mona <span>· AI</span></div><divclass="a-body">Two tasks are open: the table view sits at 65 %, the board with drag and drop is planned.</div><divclass="nk-ai-actions"><button>📋 Copy</button><button>↻ Rephrase</button></div></div></div></div><divclass="nk-ai-input-row"><inputplaceholder="Ask Mona something …"><buttonclass="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.
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<divstyle="padding-left:48px;max-width:460px"><divclass="nk-block-host"><divclass="nk-block-actions show" style="top:6px;left:-46px;pointer-events:none" aria-hidden="true"><buttontype="button" tabindex="-1">+</button><buttontype="button" class="drag" tabindex="-1">⠿</button></div><divstyle="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><divclass="nk-block-host nk-drop-target"><spanclass="nk-block-handle">⠿</span><divstyle="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.
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<divstyle="display:flex;gap:20px;flex-wrap:wrap;align-items:flex-start"><divclass="nk-slash-menu" style="box-shadow:none;border:1px solid var(--nk-border)"><divclass="nk-slash-menu-label">Basic blocks</div><divclass="nk-slash-item selected"><spanclass="m-icon">H1</span><div><div>Heading 1</div><divclass="m-desc">Big section heading</div></div></div><divclass="nk-slash-item"><spanclass="m-icon">☑</span><div><div>To-do list</div><divclass="m-desc">Track tasks with a checkbox</div></div></div><divclass="nk-slash-item"><spanclass="m-icon"></></span><div><div>Code block</div><divclass="m-desc">Capture a snippet</div></div></div></div><divclass="nk-bubble-menu" style="box-shadow:none;border:1px solid var(--nk-border)"><buttonclass="active"><b>B</b></button><button><i>I</i></button><button><s>S</s></button><button></></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.