# Oxynote design style book How the Oxynote web and desktop app looks, and the rules a new screen or component has to follow to belong in it. The document stands on its own: it needs no access to the codebase. The app defines its colours in `oklch()`. The hex values here are sRGB approximations of them, for tools that cannot take oklch. ## 1. In one paragraph A quiet, dense, neutral-grey document tool. Surfaces are near-white (or near-black) with hairline borders and almost no shadow. The only brand colour is a dark navy, used sparingly for primary buttons, links and checked states. Colour otherwise appears only when it carries meaning: status, diff, hooks, comments, tags, charts. Type is Inter throughout, small (13px is the workhorse UI size), with medium and semibold doing the work of hierarchy. Corners are tight (about 4 to 6px). Motion is short opacity fades. The closest public reference is shadcn/ui "new-york" on the neutral base, tightened and desaturated further. ## 2. Foundations and references | Thing | Choice | | --- | --- | | Component base | [shadcn-vue](https://www.shadcn-vue.com), style `new-york`, base colour `neutral`, CSS variables on. | | Primitives | [reka-ui](https://reka-ui.com) (the Vue port of Radix) | | CSS | Tailwind v4, theme declared in CSS (`@theme`), no JS config. `tw-animate-css` for enter/exit, `@tailwindcss/typography` (`prose prose-neutral`) for document content | | Variants | `class-variance-authority` + `cn()` (`tailwind-merge`) | | Font | Inter, weights 400/500/600/700, on every element. Monospace is the Tailwind default `font-mono` stack | | Icons | Iconify through `@nuxt/icon` (CSS mode) | | Toasts | `vue-sonner`, bottom-right, 5s | | Charts | ECharts; diagrams by Mermaid; both themed from the CSS tokens | | Code | highlight.js `github` / `github-dark` themes, CodeMirror for PromQL | | Editor | TipTap (ProseMirror) with Yjs collaboration | Installed shadcn components: accordion, avatar, breadcrumb, button, button-group, calendar, collapsible, dialog, drawer, dropdown-menu, empty, form, input, input-group, kbd, label, popover, select, separator, sheet, sidebar, skeleton, sonner, switch, textarea, tooltip. Reach for one of these before drawing a new control. ## 3. Colour Colours are semantic tokens with a light and a dark value. Use the token name (`bg-muted`, `text-muted-foreground`), never a raw colour. Dark mode is the `.dark` class on ``; the user picks light, dark or auto. ### 3.1 Neutral surfaces and text | Token | Light | Dark | Used for | | --- | --- | --- | --- | | `background` | `#fdfdfd` | `#121212` | page, document canvas, dialogs | | `foreground` | `#1b1b1b` | `#ffffff` | body text | | `card` | `#ffffff` | `#070707` | raised cards | | `popover` | `#fdfdfd` | `#121212` | menus, popovers, tooltips, toasts | | `sidebar` | `#f4f4f4` | `#070707` | app sidebar (a step away from the page) | | `sidebar-foreground` | `#2e2e30` | `#dedede` | sidebar text | | `muted` / `secondary` | `#f5f5f5` | `#090909` | callouts, code blocks, inset panels | | `muted-50-real` | `#f9f9f9` | `#0d0d0d` | half-strength muted as a solid colour | | `muted-highlight` | `#eeeeee` | `#111111` | code block title bar | | `accent` / `sidebar-accent` | `#e8e8e8` | `#262626` | hover, pressed and selected fills | | `muted-foreground` | `#737373` | `#a1a1a1` | secondary text, icons, placeholders | | `border` / `input` | `#e5e5e5` | `#262626` | every hairline and field outline | | `ring` | `#a1a1a1` | `#525252` | focus ring, chart crosshair | | `scrollbar` | `#c8c9cc` | `#5a5b5d` | thin scrollbar thumb on transparent track | | `background-translucent` | `#fdfdfd` 93% | `#121212` 93% | see-through bars over content | The neutrals are pure grey (zero chroma). Dark mode is not inverted light mode: the page is `#121212` while sidebar, cards and muted panels go *darker* (`#070707`, `#090909`), and hover fills go lighter (`#262626`). Text hierarchy is made with `muted-foreground` and opacity steps on it (`/70`, `/60`, `/50`), not with extra grey tokens. ### 3.2 Brand | Token | Light | Dark | Used for | | --- | --- | --- | --- | | `primary` | `#253d61` | `#253d61` | primary button, checked checkbox and switch, selected calendar day | | `primary-foreground` | `#fafafa` | `#fafafa` | text on primary | | `link` | `#253d61` | `#c2d9fc` | text links (`.link-primary`: underlined, offset 2, 80% on hover) | Navy is the only brand hue (oklch hue 258). It is the same in both modes as a fill; as text in dark mode it switches to the pale tint. There is no gradient, glow or second accent colour anywhere. ### 3.3 Status and destructive | Token | Light | Dark | | --- | --- | --- | | `status-info` | `#0071c2` | same | | `status-success` | `#27a644` | same | | `status-warning` | `#bd9f00` | same | | `status-error` | `#e7000b` | same | | `destructive` (fill) | `#e7000b` | `#82181a` | | `destructive-foreground` (text) | `#e7000b` | `#fb2c36` | | `background-warning` | `#381320` | `#d42e5b` | Status colours tint an icon or a thin border; the surrounding box stays neutral. Toasts are a neutral popover card with one coloured status icon. ### 3.4 Feature colours Each belongs to one feature. Do not borrow one for something else; a new shared meaning gets its own neutrally named token. | Feature | Tokens | Light | Dark | | --- | --- | --- | --- | | Diff | `diff-added` / `diff-removed` (block wash) | `#18831f` 30% / `#e31010` 30% | `#15ab26` 25% / `#b8170f` 25% | | | `diff-field-added` / `-removed` (field wash) | same hues at 20% | 20% / 22% | | | `diff-added-foreground` / `diff-removed-foreground` | `#0d5e13` / `#a20808` | `#85dd83` / `#f7a598` | | Hooks | `hook-status-fresh` (+ `-foreground`) | `#25984d` / `#0f5025` | `#6fd087` / `#b0ecbc` | | | `hook-status-triggered` (+ `-foreground`) | navy / navy | `#87b0ed` / `#c2d9fc` | | | `hook-status-needs-attention` (+ `-foreground`) | `#bc8218` / `#69440a` | `#f1bf5b` / `#fadea4` | | Comments | `comment-highlight` | `#ffcb00` | same | | Drag and drop | `drag-target` | `#c7e1f8` | `#3699e2` 36% | | API docs | `http-method-get/post/put/patch/delete/…` | get `#045ad0`, post `#217005`, delete `#b13600`, rest muted | get `#2b9df6`, post `#3eae20`, delete `#f27400` | | | `url-path-param` | `#8caf8c` | `#6e8a6e` | Recurring pattern for a tinted feature surface: the feature colour at 5–15% as the fill, 20–30% as the border, and the matching `-foreground` token for text. Example, a triggered hook notice: `bg-hook-status-triggered/10 text-hook-status-triggered-foreground`. Comment marks: 12% fill with a 2-unit bottom border at 35%, rising to 26% when focused. A block with both a triggered and a needs-attention hook is striped at -45° in the two colours (`bg-hook-status-mixed`). ### 3.5 Palettes - **Selectable colours** (tags, user-picked chart colours): 16 Tailwind `-600` shades in hue order: red, orange, amber, yellow, lime, green, emerald, teal, cyan, sky, blue, indigo, violet, purple, fuchsia, pink. Default is green-600. - **Chart series**: 16 Tailwind `-500` shades, ordered for contrast between neighbours: amber, teal, indigo, fuchsia, emerald, cyan, sky, blue, violet, lime, yellow, purple, orange, pink, green, red. - **Collaborator carets**: Tailwind 500–800 of 17 hues (the 16 above plus rose). - **Tag pills** derive from one stored colour. Light: 13% of the colour as fill, text is the colour mixed 80% with black, no border. Dark: 18% fill, 40% border, text mixed 60% with white. ## 4. Typography Inter everywhere. Sizes, with the extra in-between steps this app adds to Tailwind's scale: | Class | Size / line | Role | | --- | --- | --- | | `text-2xs` | 11 / 13px | section labels (uppercase, `tracking-wide`, semibold), tiny counters | | `text-xs` | 12 / 16px | secondary lines, pills, kbd, notices | | **`text-2sm`** | **13 / 15px** | **default UI text**: menus, sidebar rows, toasts, tooltips, small buttons | | `text-sm` | 14 / 20px | default buttons, inputs, callout text, code | | `text-2base` | 15 / 19px | document body, modal section headings, large inputs | | `text-base` | 16 / 24px | inputs on phones (avoids iOS zoom) | | `text-lg` | 18px | auth page headings | | `text-2xl` | 24px | document title, settings modal title | - Weights: `font-medium` (500) for control labels and item titles, `font-semibold` (600) for headings, pills and emphasis, 400 for body. Bold (700) is rare. - Document content: body `text-2base`; H1 1.5em, H2 1.3em, H3 1.1em (about 22.5 / 19.5 / 16.5px), all in `foreground`. Blocks are separated by 1.25rem. Blockquotes are upright, without quote marks. Links in prose are foreground-coloured. - Inline code: `rounded-sm border border-border bg-muted`, normal weight, padding `0.075em 0.25em`, no backticks. - No letter-spacing changes except the uppercase section labels. ## 5. Shape: radius, borders, shadows **Radius.** The base is `--radius: 0.35rem` (5.6px). | Class | Value | Used for | | --- | --- | --- | | `rounded-sm` | 1.6px | inline code, kbd, small highlights | | `rounded-md` | 3.6px | **default**: buttons, inputs, list rows, notices, diff overlays | | `rounded` | 4px | menu and select items | | `rounded-lg` | 5.6px | containers: dialogs, popovers, menus, toasts, callouts, code blocks | | `rounded-xl` | 9.6px | almost unused | | `rounded-full` | pill | avatars, tag pills, count pills, switches | Rule of thumb: controls are `md`, the box that holds them is `lg`, pills and people are `full`. Nothing is rounder than 10px except pills. **Borders.** One hairline (`border` = 1px, colour `border`) is the main way surfaces are separated: header bottom edge, dialog and popover outlines, field outlines, dividers (`h-px bg-border`). Every element defaults to `border-border`. Dashed borders mark something empty or not yet set up (empty states, a data source with no status). Stacked avatars and status badges are cut out from what is behind them with a `background`-coloured border or ring. Thicker (2-unit) borders are rare: comment marks, the data source status ring, the diff before/after frames. **Shadows.** Minimal, and never the only separator: a shadowed surface also has a border. | Shadow | Used for | | --- | --- | | `shadow-xs` | filled and outline buttons, select triggers, textareas, switches | | `shadow-md` | popovers, dropdown menus, toasts | | `shadow-lg` | dialogs, sheets | | `shadow-fully-clear` (`0 0.3125rem 1.25rem rgb(0 0 0 / .15)`) | floating elements over content | | none | cards, callouts, code blocks, sidebar, header | **Overlays.** Dialog scrim is black at 40%, sheet scrim black at 28%. No backdrop blur. ## 6. Spacing, sizing and layout Tailwind's 0.25rem unit. All lengths are `rem` or viewport proportions (`dvh`, `dvw`, `%`), never `px`. Quarter steps (`0.75`, `1.25`, `1.75`) are used freely for optical tuning. - **Gaps**: `gap-1` and `gap-2` dominate; `gap-1.5` between an icon and its label; `gap-3` to `gap-6` between groups; `gap-8` between modal sections. - **Padding**: rows and small controls `px-2 py-1`; menus and popovers `p-1` around their items; cards and panels `px-3 py-2.5` or `px-4 py-3`; dialogs `p-6`. - **Control heights**: 28px (`h-7`, compact and header controls), 32px (`h-8`, small), 36px (`h-9`, default), 40px (`h-10`, auth pages and the document header bar). - **Icon buttons**: 36, 26, 20 and 15px squares (`icon`, `icon-sm`, `icon-xsm`, `icon-2xsm`). - **Icons**: 16px default; 14px and 12px in dense rows and pills; 32px+ only in empty states. Stacked people avatars are 24px. Page skeleton: - **Sidebar**: left, `sidebar` colour, 14.5rem wide by default, resizable 12–18rem by a hairline handle that thickens on hover; 14rem sheet on phones. - **Document header**: sticky, 2.5rem tall, `background` with a bottom hairline; breadcrumbs on the left, actions on the right. - **Document canvas**: `background`, content has 1.25rem side gutters (3.125rem from `lg`); the compact view caps the column at 80rem and centres it. - **Side panels**: the AI chat is a right-hand column in the `sidebar` colour with a left hairline, 30rem wide, opening over 200ms. The notification panel is 20–26.25rem wide. On small screens both become 20rem sheets. - **Dialogs**: centred, `rounded-lg border bg-background p-6 shadow-lg`. Settings is 60rem wide and at most 80dvh; search is 40rem wide, pinned at 15dvh from the top, at most 70dvh. - **Auth pages**: a single centred column about 16.75rem wide, logo at 3rem, full-width 40px buttons stacked with `gap-3`. Breakpoints are Tailwind's (`sm` 40rem, `md` 48rem, `lg` 64rem); `lg` is the main desktop switch. A 30rem cut handles phones, and `@media (hover: none)` hides keyboard hints and hover-only affordances on touch devices. Stacking order, low to high: editor content, navbar (10), sidebar and drag handle (20), comment indicators (25), dropdowns and popovers (30), sheets (40), modals and tooltips (50), toasts and dragged items (500). Use the named `z-*` utilities (`z-navbar`, `z-popover`, `z-modal`, …). ## 7. Components **Buttons.** Base: `rounded-md text-sm font-medium`, icon 16px, `gap-2`. Hover and pressed are opacity steps of the same fill, not a new colour. Disabled is 50% opacity. | Variant | Look | | --- | --- | | `default` | navy fill, near-white text; 80% on hover, 60% pressed | | `secondary` | `secondary` grey fill | | `outline` | hairline border on `background`; `accent/70` on hover | | `outline-no-effect` | outline with no hover state | | `ghost` | no fill; `accent/70` on hover, `accent` pressed, `accent/50` when active | | `ghost-plain`, `outline-transparent` | no fill ever; only the text dims on hover | | `dim`, `outline-dim` | text at 30%, for controls that should recede until needed | | `destructive` | red fill, white text | | `ghost-plain-destructive` | red text, no fill | | `link` | link colour, underline on hover | Sizes: `default` 36px, `sm` 32px, `2sm` 28px with 13px text, `lg` 40px. **Inputs.** `rounded-md border border-input bg-transparent px-3 py-1`, `muted-foreground` placeholder. Focus: border turns `ring` with a 3px `ring/50` halo. Invalid: `destructive` border. Text is 16px on phones and 14px from `md`. In dark mode textareas and outline buttons get a faint `input/30` fill. **Checkbox.** 16px square, `rounded-md`, hairline; checked is navy with a square-capped white tick that fades in over 150ms. A checked task-list item's text gets a strike-through and drops to 50% opacity. **Switch.** Pill, about 18 × 32px, navy when on, `input` grey when off. **Menus, selects, popovers.** Container `rounded-lg border bg-popover p-1 shadow-md`. Items `rounded px-2 py-1.25 text-2sm`, icon `muted-foreground` 16px, `gap-1.5`; highlighted `accent/50`, pressed `accent`. Destructive items are red text with a `destructive/10` highlight. Separators are a full-bleed hairline. **Tooltips.** `rounded-md border bg-popover px-3 py-1.5 text-2sm`; neutral, not inverted. Shortcuts shown inside use Kbd. **Kbd.** `h-5 min-w-5 rounded-sm border bg-muted px-1 text-xs font-medium text-muted-foreground`. **Sidebar rows.** 28–32px tall, `rounded-md`, 13px text. Hover `sidebar-accent/50`, active document `sidebar-accent/50`, pressed `sidebar-accent`. Hovering a row swaps its icon for a collapse chevron and fades in its actions menu. Group labels are 12px medium at 70%. **List and result rows** (search, notifications). `rounded-md`, selected `accent/50`, pressed `accent`; hover styling only on devices that hover. Matched text is a `comment-highlight/40` mark. **Toasts.** `rounded-lg border bg-popover px-3 py-2.5 shadow-md`, at least 23.75rem wide: 16px filled status icon, 13px medium title, 13px muted description, plain close button. **Pills.** `rounded-full px-1.75 py-0.5 text-xs font-semibold`. Tag pills are tinted (section 3.5); a neutral pill is `bg-muted text-muted-foreground`. Diff count pills are a bordered capsule split into a red and a green half. **Settings sections.** A 15px heading over a `rounded-md border bg-muted/50 px-4 py-3` panel of rows. **Avatars.** Circles, 32px by default; generated default avatars for users and workspaces without a picture. **Empty states.** Centred, a 32px icon at 50% opacity over one line of 15px muted text. **Loading.** One spinner everywhere: `svg-spinners:blocks-shuffle-3`. Skeletons are `rounded-md bg-primary/10` with a pulse. **Editor blocks.** - Callout: `rounded-lg bg-muted`, no border, icon at the top left, 14px text. - Code block: `rounded-lg bg-muted`, monospace 13–14px, no border. With a title: a `muted-highlight` title bar, and the whole block gains a hairline. - Hook notice: `rounded-md` tinted box (section 3.4), 12px text, 14px status icon, optional full-width action footer divided by a top border. - Diff: added and removed blocks get a `rounded-md` translucent green or red wash behind them; changed text gets the same wash inline; a changed block shows a pill marker that opens a before/after card. - Comments: yellow underline-and-wash marks in text, a yellow wash over commented blocks, indicators in the right margin. ## 8. Icons - Chrome (toolbars, menus, buttons, sidebar) uses **outline stroke icons**. Lucide is what most of the existing chrome uses, but the app is moving off it: prefer **MingCute** `-line` icons for anything new, and match the stroke style of the neighbours. - **Filled** MingCute icons (`-fill`) are for status glyphs (check, close, info and warning circles), file types and selected states. - Brand marks come from `simple-icons`; language and tool logos from `devicon`. - Icons inherit text colour; in menus they are `muted-foreground`. - The app logo is a custom SVG mark (with an "ink" variant for the single-colour version). - A block type has exactly one icon, used everywhere that block is named. ## 9. Motion - Hover and state changes: `transition-colors` or `transition-opacity`, 150ms. - Overlays (dialog, popover, menu, tooltip): fade in and out only. No zoom, no slide. - Sheets slide from their edge: 500ms in, 300ms out. - Sidebar resize and collapse: 200ms. Accordions: 200ms ease-out. - Page content mounts hidden and fades in over 300ms once every block has settled, so layout never jumps in view. - Things that appear on hover (row actions, drag handle, block buttons) fade; they do not move. - No bounces, springs or decorative animation in the chrome. ## 10. States and accessibility - **Hover** `accent` at 50–70%; **pressed** full `accent`; **selected or active** `accent/50`. Filled buttons step their own fill to 80% and 60%. - **Focus**: 3px `ring/50` halo plus `ring` border on fields; 2px ring on sidebar and icon controls. - **Disabled**: 50% opacity, `not-allowed` cursor. - **Invalid**: `destructive` border, faint destructive ring. - Buttons, menu items and checkboxes show `cursor-pointer`. - Every dialog and sheet has a title and a description (visually hidden when nothing visible plays the role). Icon-only buttons carry an `sr-only` label. - Scrollbars are thin, `scrollbar` thumb on a transparent track. ## 11. Rules for new work 1. Use tokens and the type scale above. No raw hex, no one-off greys, no new font sizes. 2. Lengths in `rem`, `dvh`, `dvw` or `%`. Never `px`. 3. Tailwind utilities in the template. A custom rule goes in the one global stylesheet, only when utilities cannot express it; no `