Ian Bone
Case study · 06 · BONES v9.0.0

BONES DesignSystem

The design system this site is built on — a token pipeline that compiles to the CSS you’re reading, documented live from its own source.

Role
Design & Engineering
Year
2026
Components
56
Palettes
Ink · Night · Reef · Sakura
An illustrated collage of design-system parts: alignment grids, colour tokens and a component library
  • Design tokens
  • Documentation
  • Build tooling
  • Meta
Overview

BONES is the design system behind ianbone.com, and it is also a case study about itself. The tokens that style every page are the same tokens documented on this page, compiled through a build step and rendered live in front of you. Now that AI reads code about as fluently as people do, a system that stays legible right down in its source is a system that keeps scaling, and this page is the proof of that.

01

Pipeline

One TypeScript file holds every token. A build step compiles it into a Tailwind theme, the whole site is styled from that theme, and this page reads the same file again to document itself — so nothing here can drift from what the site ships. Send a palette through it.

01 · Source

tokens.ts

c('pop',
  'The hue, once a page.',
  'lime.300')
02 · Build

build-tokens

$ npm run build:tokens
✓ 36 colours × 4
✓ contrast AA, every palette
03 · Output

tokens.css

--color-pop:
  #d0fe91;
--color-surface:
  #ffffff;
04 · Live

This page

Paper + ink
Try it — send a palette through the pipeline

02

Tokens

Primitives are raw values with no job: the colour scales, one size scale, the weights and families. The semantic layer gives each token a job and points it back at a primitive — a different one per palette. Components only touch the semantic layer, so a palette change repaints the site without editing a component.

Anatomy of a token · pop
PalettePrimitiveValueSemanticUtility
Inklime.300#d0fe91--color-popbg-pop
Nightlime.300#d0fe91--color-popbg-pop
Reeflime.50#f7ffec--color-popbg-pop
Sakurasakura.300#ff9cba--color-popbg-pop
Colour primitives · 3 scales → 32 semantic tokens
sumi
  • 0
  • 50
  • 100
  • 200
  • 300
  • 400
  • 500
  • 600
  • 700
  • 800
  • 900
lime
  • 50
  • 100
  • 200
  • 300
  • 400
  • 500
  • 600
  • 700
  • 800
  • 900
sakura
  • 50
  • 100
  • 200
  • 300
  • 400
  • 500
  • 600
  • 700
  • 800
  • 900
03

Palettes

Four palettes from the one file. Ink and Night are drawn by hand; Reef and Sakura are the printed page re-inked — derived by remapping its scale steps, so each is a handful of references rather than a second colour table. Each preview below is pinned to its palette and painted by the same tokens as the page around it.

  • The printed page

    Ink

    Black ink on white paper, one lime highlight.

  • The negative page

    Night

    White ink on a black ground.

  • The lime page

    Reef

    Ink on Reef lime; the highlight turns to ink.

  • The pink page

    Sakura

    Plum ink on blossom paper.

04

Type

Display, interface and prose all resolve to Geist Sans, so hierarchy comes from size, weight and tracking — and the v9 scale is steep: one hero, one headline per sheet, then a big drop to reading size. Geist Mono is the label voice and machine output. 21 steps, both faces self-hosted, no third-party request.

display--font-display
Good bonestext-hero · 700The splash headline: the homepage hero and a case-study title. Once per page.
Good bonestext-section · 700A sheet headline — the one big line that opens each section.
Good bonestext-wordmark · 800The sign-off name across the foot of the page, outlined. Once per page.
Good bonestext-stat · 700A counted number in a stats band.
Good bonestext-sfx · 800The giant section word — lime fill, ink outline. One per page.
Good bonestext-display-xl · 700The homepage headline. Once per site.
Good bonestext-display · 700Section and interior page headlines.
heading--font-ui
Good bonestext-title · 700Section titles.
Good bonestext-heading · 600Sub-section headings, card titles.
Good bonestext-balloon · 600Balloon and caption text. Sentence case.
Good bonestext-subhead · 600The smallest heading — list headers.
Good bonestext-meta · 400Nav, table cells, captions.
Good bonestext-annotation · 500Uppercase markers, button labels, eyebrows.
Good bonestext-fig · 600Page numbers, chapter marks, figure marks.
prose--font-prose
Good bonestext-lede · 400The intro beside a sheet headline.
Good bonestext-read · 400Case-study reading text — long-form, in the read measure.
Good bonestext-body-lg · 400The one lede.
Good bonestext-body · 400Default reading text.
Good bonestext-body-sm · 400Captions and secondary prose.
label--font-label
Good bonestext-label · 400Eyebrows, fact keys, chapter and figure marks. Always uppercase.
terminal--font-terminal
Good bonestext-code · 400Code blocks, token readouts.
05

Components

56 components in 7 categories. Each lives in its own folder with its implementation, a machine-readable doc and a barrel export. Every page on this site is composed from them and nothing else. A few, live:

Button
Restyle
Eyebrow · Tag
01 WorkDesign systemsFront end
Skull · Bubble

Hover the work to see it in colour.

PullStat

56components, documented live

Placeholder
Layout · 3
Sheet
A full-bleed section on a paper, ink or pop ground. Inside `Sheets`, each sheet rises over the last on a tilted edge, like a page turning.
MangaPage
A section laid out like a manga page: a 12-column panel grid split by white gutters, a giant section word across its top edge, and a boxed title tab in the bottom margin. Inks its panels in as they scroll into view.
Section
The site's repeated section shell: a measured <section> that opens with an indexed eyebrow and an optional title, then its content.
Typography · 8
Headline
A display headline set as authored lines, each sliding up out of its own mask as it scrolls into view.
InkText
A large paragraph whose words ink in, in reading order, as it scrolls through the window.
Balloon
A word balloon — speech, thought, or shout — drawn in SVG behind real, selectable text in the site sans.
Caption
A caption box — the manga narrator's rectangle, used for labels and short plain statements, in paper or reversed ink.
Sfx
The giant section word: heavy sans letters filled with the lime highlight and outlined in ink, the manga sound-effect device in a professional voice. Decorative.
Eyebrow
The one label voice: an ink square and mono tracked caps. Opens a sheet ("01 — Work"), keys a fact, marks a chapter or figure.
Heading
A heading bound to a type role (display-xl → subhead). Picks a sensible element per role; override with `as`. `chip` sets it on paper over screentone.
Text
Reading, label and code text bound to a type role and tone. Body roles read in the prose face, labels in the ui face (annotation uppercased), code in Geist Mono. `chip` sets it on paper over screentone.
Container · 12
ArtPanel
Colour art in an ink frame, with an optional diagonal edge. Inked in across the panel as it arrives, then its frame drawn round it.
WorkGrid
The selected-work index: art tiles in two columns split by a diagonal gutter, each with an index, title, tags, a counted stat and floating labels.
ChatPanel
A chat in square ink lines: a header with the guide’s name and what it can see, a scrolling log, suggestion chips, and an input with Send.
Gallery
A strip of figures cut on shared diagonals like comic panels, with figure marks, a caption line and a lightbox.
Placeholder
A deliberate empty frame where a screenshot will go, named so it reads as planned rather than missing.
Redaction
The NDA panel that stands where hero art would: bars black out line by line beside a plain note on what isn’t shown.
Panel
A manga panel: a grid cell framed in the heaviest line on the page, optionally cut on the diagonal, filled with screentone or motion lines, or inverted to black — with an overlay slot for balloons that break across the frame.
Reel
A row of white panels cut at 22° — a fixed label, then a strip of logos or wordmarks that loops sideways behind its diagonal.
Figure
A numbered plate: a label row above a panel-weight ink frame, with an optional caption below. For framed illustrations outside the panel grid.
Window
A titled frame: a panel-weight ink frame with a black caption strip (title, glyph, minimise and close), an optional menu strip, a body and an optional status bar. Draws itself open the first time it scrolls into view.
Card
A framed block outside the panel grid — a panel-weight ink frame on paper, with an optional indexed eyebrow, title and body. With `href` the whole card is one link with the lime hover.
Callout
An aside on a panel-weight ink rule, on paper — a note, a caveat, the NDA banner, or at `lg` a pulled-out outcome line.
Display · 23
StatBand
A row of big numbers that count up as they arrive, each over a mono label.
Marquee
A strip of client marks sliding left forever, separated by 22° ink strokes and faded at both ends.
Facts
A description list of facts keyed by mono labels — across under an ink rule, or down a column.
Sequence
An open timeline — a hair rule drawn left to right, a tick per step, then a mark and a title — with the current step on an ink rule and a filled tick.
Pipeline
The BONES token pipeline as a live diagram — tokens.ts → build-tokens → tokens.css → this page. Send a palette down it and the site repaints when it arrives.
Wordmark
The sign-off: the name set huge and outlined in ink across the width, the skull beside it.
Skull
The header skull, alive: pupils follow the pointer, it blinks, and its jaw chatters while it talks. The Logo and the site guide are one character.
Bubble
One short spoken line in a square ink box, with a pointer towards the speaker.
IndexList
A contents page for the work: one row per item, a big numeral, a title and a line, under an ink rule.
Points
Columns of short points under an ink rule: a mark, a title and a line each, divided by hairlines.
LayerStack
Layers stacked on parallel diagonal seams, the one that was the work in black; builds from the foundation up as it scrolls by.
Route
A process as a journey: a line that draws along its stops as it scrolls by, each stop lighting as it is reached.
PullStat
One real figure pulled out of the read: a big number beside its label on the pop ground, cut on a diagonal.
Halftone
A photo printed like a manga page: greyscale, thresholded into black dots whose size follows the image. An optional treatment for when an image should read as print rather than colour art.
Annotation
Two marks borrowed from technical drawing: a note tethered by a leader line, and a measured span.
StatusBar
The strip along the foot of a Window: short readouts split by hairlines under an ink rule, with one cell allowed to stretch.
Tag
Small chip for disciplines, statuses, and categories. A flat grey fill with 4px corners and no border — the one soft, toned element on a black-and-white page.
Icon
A line glyph on a 24-unit grid — 2-unit stroke, square ends, mitred corners, in a semantic icon tone. The one icon set: panel marks and the interface glyphs controls are built from.
Stat
A single figure and what it counts. Plain, a pull stat — a display-size value over a soft label. With `icon`, the content of a stat panel: tile, number, title.
IconTile
A line icon in ink on a lime tile — the mark at the top of a stat or step panel. One of the three places the highlight reaches the page.
Logo
The skull mark: lime linework (`highlight-mark`, lime.400), no edge. One of the places the highlight reaches the page.
Table
A tabular spec — an uppercase head over an ink rule, rows split by hairlines. Scrolls sideways on a narrow screen rather than crush its columns.
Redacted
Black bars where confidential specifics would be. Decorative — it shows that something is withheld, not what.
Navigation · 4
ChapterRail
A sticky chapter list beside a reading column: the current chapter marked, and a meter of how far through you are.
NextCase
The way on from a study: the next title huge as a link, its art cut on a diagonal, and the way back to all work.
Link
A text link. `inline` is underlined ink that takes the lime highlight on hover, like every action; `nav` is the header's quiet soft-ink item. next/link for internal routes, a safe anchor for external URLs.
MenuBar
A menu bar: a paper strip under an ink rule, holding pull-down menus that actually pull down, fully keyboard-driven, with a slot on the right for real controls. Open menus and active items take the lime highlight.
Action · 2
Button
The system's one interactive control: square, framed in ink, and filled with the lime highlight on hover. Renders a Next.js link for an internal `href`, an anchor for any other `href`, and a native button otherwise.
Restyle
The palette picker: repaints the whole site in Ink, Night, Reef or Sakura by switching `data-theme`, so every semantic token re-binds and no component changes.
Data entry · 4
Field
Labeled text input. An uppercase label above a square, ink-framed paper field, the system focus outline, and an optional hint. Generates and links an id from the label when none is given.
TextArea
Labeled multi-line input. Same label, id, hint and ink-framed face as Field, resizable vertically.
Select
Labeled native <select>. Same label, id, hint and ink-framed face as Field, with a line chevron in place of the platform arrow. Takes an `options` array or <option> children.
Checkbox
Labeled checkbox. A square ink-framed box that fills with ink and shows a paper tick when checked, label to the right, optional hint below.
06

Docs

Every component ships a doc beside it that a person or a model can read to use it correctly without opening the implementation. The schema is a TypeScript interface, so a doc can’t leave out the parts a reader needs: a doc that type-checks is complete. Button is the worked example.

The contract · ComponentDoc
FieldTypeWhat it carries
namestringThe exported component name, unique across the system.
categoryComponentCategoryIts slot in the mental model — drives grouping.
descriptionstringOne-line summary, used in listings and headings.
keywordsstring[]Search terms and synonyms an agent might try.
usageUsageDocThe narrative: fuller description, do/don’t guidance, anatomy.
propsPropDoc[]Every public prop — name, type, default, whether required.
examplesExampleDoc[]Copy-pasteable JSX, simplest first.
accessibilitystring[]Keyboard, focus, and screen-reader guarantees.
themingThemingDoc?CSS vars a theme may override, when the component exposes any.
  • DoButtonReserve the accent variant for the one primary action in a view.
  • DoAndWrite labels that name the action ("See the work", "Get in touch"), not "Click here".
  • Don’tNeverStack two accent buttons side by side; it flattens the hierarchy.
Button · props
PropTypeDefaultDescription
variant'solid' | 'accent' | 'outline' | 'ghost''accent'Visual weight, descending. accent is a solid ink fill (solid is an alias for it); outline is paper with an ink frame; ghost is flat until hover. All of them hover to the lime highlight.
size'sm' | 'md' | 'lg''md'Padding and type scale. lg is for a single headline action, like the contact email.
hrefstring—When provided, renders an <a> instead of a <button>.
disabledbooleanfalseDisables the native button.
children *ReactNode—Button label.
Button’s full machine-readable spec
{
  "name": "Button",
  "category": "Action",
  "description": "The system's one interactive control: square, framed in ink, and filled with the lime highlight on hover. Renders a Next.js link for an internal `href`, an anchor for any other `href`, and a native button otherwise.",
  "keywords": [
    "button",
    "cta",
    "action",
    "link",
    "submit"
  ],
  "usage": {
    "description": "Use Button for every action a view offers — opening a case study, sending an email, submitting a form, flipping the theme. `outline` (paper fill, ink frame) is the house button on the manga pages; `accent` is a solid ink fill for the single most important action on a screen. Every variant shares one hover, the lime highlight, so an action is the one thing on the page that lights up.",
    "bestPractices": [
      {
        "guidance": true,
        "description": "Reserve the accent variant for the one primary action in a view."
      },
      {
        "guidance": true,
        "description": "Write labels that name the action (\"See the work\", \"Get in touch\"), not \"Click here\"."
      },
      {
        "guidance": true,
        "description": "Set `href` when the control navigates — it renders a real <a> with correct semantics."
      },
      {
        "guidance": false,
        "description": "Stack two accent buttons side by side; it flattens the hierarchy."
      },
      {
        "guidance": false,
        "description": "Hand-roll a button with its own border and hover. Use Button, so every action shares the one lime hover."
      },
      {
        "guidance": false,
        "description": "Use the lime highlight at rest. It is the hover state; a lime button at rest stops meaning \"you can press this\"."
      }
    ],
    "anatomy": [
      {
        "name": "Label",
        "required": true,
        "description": "The visible text; also the accessible name."
      },
      {
        "name": "Icon / glyph",
        "required": false,
        "description": "An optional leading or trailing mark passed as children (e.g. an arrow)."
      }
    ]
  },
  "props": [
    {
      "name": "variant",
      "type": "'solid' | 'accent' | 'outline' | 'ghost'",
      "description": "Visual weight, descending. accent is a solid ink fill (solid is an alias for it); outline is paper with an ink frame; ghost is flat until hover. All of them hover to the lime highlight.",
      "default": "'accent'"
    },
    {
      "name": "size",
      "type": "'sm' | 'md' | 'lg'",
      "description": "Padding and type scale. lg is for a single headline action, like the contact email.",
      "default": "'md'"
    },
    {
      "name": "href",
      "type": "string",
      "description": "When provided, renders an <a> instead of a <button>."
    },
    {
      "name": "disabled",
      "type": "boolean",
      "description": "Disables the native button.",
      "default": "false"
    },
    {
      "name": "children",
      "type": "ReactNode",
      "description": "Button label.",
      "required": true
    }
  ],
  "examples": [
    {
      "label": "Primary",
      "code": "<Button>See the work</Button>"
    },
    {
      "label": "Case-study link",
      "code": "<Button variant=\"outline\" href=\"/work/spabreaks/\">Read the case study →</Button>"
    },
    {
      "label": "Headline action",
      "code": "<Button variant=\"outline\" size=\"lg\" href=\"mailto:…\">Email me →</Button>"
    },
    {
      "label": "Ghost",
      "code": "<Button variant=\"ghost\" size=\"sm\">Dismiss</Button>"
    }
  ],
  "accessibility": [
    "Renders a native <button> or <a> — full keyboard and screen-reader support for free.",
    "Focus draws the brand-bound outline inside the button edge, so it never shifts the layout or clips against a neighbour.",
    "The lime hover carries ink type at 17:1, in both themes and inside `.inverted`.",
    "Disabled state sets the native disabled attribute, not just styling."
  ],
  "theming": {
    "vars": [
      {
        "name": "--color-highlight",
        "description": "The hover fill of every variant. Lime in every theme.",
        "default": "#d0fe91"
      },
      {
        "name": "--color-on-highlight",
        "description": "Type on the hover fill. Always ink.",
        "default": "#0a0a0a"
      },
      {
        "name": "--color-accent",
        "description": "Fill of the accent/solid variant. Re-bound by .inverted.",
        "default": "#0a0a0a"
      },
      {
        "name": "--color-ink",
        "description": "The frame of accent/solid/outline. Re-bound by .inverted.",
        "default": "#0a0a0a"
      },
      {
        "name": "--duration-fast",
        "description": "Speed of the colour transition on hover/focus.",
        "default": "120ms"
      }
    ]
  }
}
07

Principles

The rules BONES runs on: two tiers, semantic tokens over raw values, one ink, a light/dark pair, and a system legible to people and models at once.

  • 01Two tiers, and only twoEvery value lives in src/design/tokens.ts as one of two things. Primitives are raw — a hex, a rem, a duration — with no job attached. Semantic tokens reference a primitive and name a job: text, background, border, stroke, spacing, a type role.
  • 02One inkThe site is drawn like a manga volume: black ink on white paper. There is one hue, lime, and it has one narrow job through the `highlight` token: the fill of the giant section words, an action on hover, and the tile behind a stat icon. It is never text and never a panel fill. A grey area is never a flat fill (the Tag chip is the one exception) — it is screentone, a field of ink dots (tone-light, tone-mid, tone-dense). Art and photos are the exception: they keep their own colour, framed by a panel, so the UI around them stays ink and lime.
  • 03Depth is line weightManga has no drop shadows. A thing is in front because its line is heavier, or because it overlaps a panel border. The stroke tokens rank the page: --stroke-panel is the heaviest, --stroke-balloon next, --stroke-hair for interface rules. Elevation tokens still exist so components can ask for a height, and every height binds to none.
  • 04Manga in the form, professional in the voiceThe manga lives in the layout: panels, diagonal gutters, screentone, balloons crossing the frame, a giant section word across each page. The words do not play along. Copy is plain first person, the way it would read on a CV — no chapter titles, catchphrases or sound effects.
  • 05Legible to people and machinesEvery design file opens with a JSDoc header (@file / @input / @output / @position) and a SYNC note naming what must move with it. Every component ships a typed doc (Name.doc.ts) an agent can read to use it correctly without opening the source.
01 Next case study

Let’s buildsomething withgood bones.

bones.ianc@gmail.com
© 2026 Ian BoneDrawn in BONES, this site’s own design system