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#f7f6f3#202020Sidebar and settings nav
--nk-bg-hoverrgba(0,0,0,0.045)rgba(255,255,255,0.055)Hover wash — a darkening, never a hue change
--nk-bg-activergba(0,0,0,0.06)rgba(255,255,255,0.08)Active/selected row
--nk-bg-callout#f1f1ef#252525Callouts, badges, key caps, segmented track
--nk-bg-code#f7f6f3#202020Code block background
--nk-bg-card#ffffff#202020Board cards — separate from --nk-bg so dark mode can lift them
--nk-text#37352frgba(255,255,255,0.81)Body text. Never pure black
--nk-text-secondaryrgba(55,53,47,0.65)rgba(255,255,255,0.46)Secondary text, inactive tree items
--nk-text-tertiaryrgba(55,53,47,0.45)rgba(255,255,255,0.28)Meta, placeholders, captions
--nk-borderrgba(55,53,47,0.09)rgba(255,255,255,0.094)Hairline separators
--nk-border-strongrgba(55,53,47,0.16)rgba(255,255,255,0.16)Input borders, scrollbar thumbs, dashed frames
--nk-accent#2383e2#529CCAThe one brand colour. Everything accented is mixed from it
--nk-on-accent#ffffffText and marks on top of the accent or danger fill
--nk-danger#eb5757Destructive actions, inline code, negative deltas
--nk-tag-blue-bg#e7f3f8#133040Tag background, also the progress track
--nk-tag-blue-text#337ea9#529CCATag text, also the progress fill
--nk-tag-green-bg#edf3ec#173B2CTag background
--nk-tag-green-text#448361#4DAB9ATag text, also the positive delta
--nk-tag-orange-bg#fbecdd#40291BTag background
--nk-tag-orange-text#d9730d#FFA344Tag text, also date mentions and code attributes
--nk-tag-purple-bg#f6f3f9#2E2440Tag background
--nk-tag-purple-text#9065b0#9A6DD7Tag text
--nk-decor-purple#9065b0Avatar gradient start and cover. Same in both themes on purpose
--nk-decor-blue#529ccaAvatar gradient end and cover
--nk-decor-orange#d9730dCover 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-width260pxSidebar width, also its min-width
--nk-radius6pxControl radius. Cards and modals use 8–12px directly
--nk-fontui-sans-serif, -apple-system, "Segoe UI", Inter, Helvetica, Arial, sans-serifSystem font stack
--nk-font-monoui-monospace, "SF Mono", Menlo, Consolas, monospaceMonospace 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-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
.activenk-tree-item nk-db-tab nk-tab nk-settings-pane nk-segmented button nk-emoji-cats span nk-board nk-bubble-menu buttonMarks the current item. Tree items get the active background, tabs get the underline, panes become visible.
.opennk-modal-backdrop nk-cmdk-backdrop nk-toggle-arrowFades the overlay in and makes it interactive; rotates the toggle arrow by 90°.
.collapsednk-tree-childrenFolds a subtree away with display: none.
.selectednk-cmdk-item nk-model-card nk-slash-itemThe keyboard-highlighted or chosen option. Distinct from active: selection is transient, active is where you are.
.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-bg12.2611.78
--nk-text / --nk-bg-sidebar11.3511.06
--nk-text-secondary / --nk-bg4.194.63
--nk-text-tertiary / --nk-bg2.492.53
--nk-accent / --nk-bg3.885.83
--nk-on-accent / --nk-accent3.883.01
--nk-danger / --nk-bg3.485.05
tag blue3.954.57
tag green3.994.48
tag orange2.836.82
tag purple4.093.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<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 — 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.

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).

📊 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-app" style="height:100%"> <aside class="nk-sidebar"> <div class="nk-workspace"><div class="avatar">A</div>Acme Inc<span class="chev"></span></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"> <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 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).
.nk-app.nk-sidebar.nk-sidebar-scroll.nk-sidebar-footer.nk-main

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 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.

📊 Project overview/🚀 Roadmap
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"> <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"></button> </div> </header>
On a small screen:Stays put. The breadcrumb wraps its crumbs; drop crumbs yourself if the trail gets long.
.nk-topbar.nk-topbar-actions.nk-topbar-btn.nk-share-btn.nk-theme-toggle

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 .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<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.
.nk-page-scroll.nk-page.nk-page-icon.nk-page-title.nk-page-meta

Cover .nk-cover

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.

html<div class="nk-cover" style="border-radius:var(--nk-radius)"></div>
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<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

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="mini-avatar" style="background:var(--nk-decor-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

Database views

PRD 5.5

View tabs .nk-db-tabs

The strip above a database. The active tab is marked by a 2px underline in text colour, not by a fill. 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

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.

📄NameStatus 👤Owner📅Due 📊Progress
🚀 RoadmapDone SLSara 12.05.2026 100 %
🎨 Design systemIn progress TWTom 20.05.2026 65 %
+ + New page
html<div class="nk-table-wrap"><table class="nk-table"> <thead><tr> <th><span class="th-icon">📄</span>Name</th><th><span class="th-icon"></span>Status</th> <th><span class="th-icon">👤</span>Owner</th><th><span class="th-icon">📅</span>Due</th> <th><span class="th-icon">📊</span>Progress</th> </tr></thead> <tbody> <tr><td><span class="row-title">🚀 Roadmap</span></td><td><span class="nk-tag green">Done</span></td> <td><span class="person-cell"><span class="mini-avatar" style="background:var(--nk-decor-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></tr> <tr><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="mini-avatar" style="background:var(--nk-decor-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></tr> </tbody> </table> <div class="new-row">+ + New page</div></div>
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.th-icon.row-title.date-cell.person-cell.new-row

Tags .nk-tag

Four semantic colours, each a background/text pair per theme. The pairs are tuned separately for light and dark rather than being derived by opacity.

In progress Done Planned Design system
html<span class="nk-tag blue">In progress</span> <span class="nk-tag green">Done</span> <span class="nk-tag orange">Planned</span> <span class="nk-tag purple">Design system</span>
On a small screen:Unchanged. Inline-block, so a row of tags wraps.
.nk-tag.blue.green.orange.purple

Progress bar .nk-progress

A 6px rail whose fill is an <i> with a percentage width. Track and fill use the blue tag pair, so a re-theme carries them along.

65 %

20 %
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>
On a small screen:Fixed 110px width so it stays legible in a table cell. Override for full-width use.
.nk-progress.nk-progress-label

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="mini-avatar" style="background:var(--nk-decor-blue);width:18px;height:18px;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;font-size:9px;color:var(--nk-on-accent)">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

Forms & settings

PRD 5.6

Inputs, textarea, select .nk-input

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<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

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.

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 near 30px, 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.

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>
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.

html<div style="display:flex;gap:32px;flex-wrap:wrap"> <div> <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>
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.

Display name
How you appear in the workspace.
Email
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>
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.

AL
html<div class="nk-profile-row"> <div class="big-avatar">AL</div> <div style="display:flex;gap:8px;flex-wrap:wrap"> <button class="nk-btn secondary small">Upload image</button> <button class="nk-btn secondary small">Remove</button> </div> </div>
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<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="mini-avatar" style="background:var(--nk-decor-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="mini-avatar" style="background:var(--nk-decor-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 role select is pushed right by margin-left:auto; on narrow rows let it wrap.
.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.

Pages
✏️Rename⌘⇧R
📄Duplicate⌘D
🔗Copy link⌘L
🗑Move to trash
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 danger"><span class="m-icon">🗑</span>Move to trash</div> </div>
On a small screen:Shortcuts are meaningless on touch — hide the .m-shortcut spans there.
.nk-menu.nk-menu-item.m-icon.m-shortcut.danger.nk-menu-sep.nk-menu-label

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

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.

Settings saved
html<div class="nk-toast show" style="position:relative;bottom:auto;left:auto;transform:none;display:inline-flex"><span>Settings saved</span></div>
On a small screen:Centred by translateX(-50%), so it stays centred at any width.
.nk-toast.show

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="mini-avatar" style="background:var(--nk-decor-purple)">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. 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<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"> <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.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