← All work
04BONES v5.0.0 · overview

BONES Design System

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/What BONES is

BONES is the design system behind ianbone.com. One TypeScript file holds every token. A build step compiles that file into a Tailwind theme, the whole site is styled from that theme, and this page reads the same file again to document itself. That’s four stages running off a single source of truth, so nothing you read here can drift from what the site actually ships.

01
tokens.ts
typed source of truth
02
build-tokens
compiles → CSS
03
tokens.css
styles the site
04
this page
renders the source

02/Tokens · primitives → semantic

There are two tiers, and only two. Primitives are raw values with no job attached — tonal colour scales, one size scale, the type weights and families. A primitive only says what it is, never where it’s used. The semantic layer is where each token gets a job and points back at a primitive, resolving to a different step depending on the theme. Components only ever touch the semantic layer, so flipping the theme repaints the whole system without editing a single component.

Anatomy of a token

Colour
Type
Spacing
Grid
Motion
Raw value · PC
#d0fe91
Primitive · PC
lime.300
Raw value · Mac
#d0fe91
Primitive · Mac
lime.300
Semantic · both
color.background.accent
Sourcetokens.ts
CSS variable--color-accent
Utilitybg-accent

One primitive, one semantic name. Every surface that reaches for color.background.accent updates at once — and it resolves to a different primitive per theme without any component knowing.

Foundations at a glance

4 scales
26 semantic colour tokens · 2 skins
3 type roles
7 steps · one shell face
0.25rem base
8 spacing steps · square corners
12 columns
1.5rem gutter · press G to overlay

Every scale, step, and swatch is enumerated in the source — src/design/tokens.ts — rather than reprinted here.

03/Components · 14

A design system is more than its tokens. These are the composable parts built on top of them — 14 components across 7 categories. Each one lives in its own folder with the component, a machine-readable doc, and a barrel export, and each ships a live specimen under both skins. Here they’re mapped, not rendered — one is worked through in full below.

Action · 1

Button

The system's one interactive control. Square, bevelled, uppercase. Renders as an anchor when `href` is set, a native button otherwise.

Navigation · 1

Link

A styled inline link, drawn the way the shell drew one: the accent colour, underlined, inverting to a selection fill on hover. next/link for internal routes, a safe anchor for external URLs.

Typography · 2

Heading

A heading bound to a display type role (display / title / heading). Picks a sensible element per role; override with `as`.

Text

Reading and label text bound to a type role and tone. Every role is set in the shell face; annotation is uppercased and tracked.

Display · 3

Tag

Small chip for disciplines, statuses, and categories. Square like everything else, lifted by the 1px bevel rather than a border.

Icon

A pixel-art glyph painted with a semantic icon token. A thin wrapper over PixelIcon that adds tone and repaints per theme.

Stat

A single headline figure — a big value with an accent left-rule and a caption. The shape case studies store in section.stat.

Container · 2

Card

A raised, bevelled panel — the system's core container. Optional indexed eyebrow, label, and title; becomes a link when `href` is set.

Callout

A raised aside with an accent (or neutral) left-rule — a note, a tip, or a confidential NDA banner. Shaped like a message box: bevel, no shadow.

Layout · 1

Section

The site's repeated section shell: a measured <section> that opens with an indexed eyebrow and an optional title, then its content.

Data entry · 4

Field

Labeled text input. Label above a sunken white field, the skin-bound 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/focus pattern as Field, with a vertically resizable textarea.

Select

Labeled native <select>. Same label/id/focus pattern as Field. Takes an `options` array or <option> children.

Checkbox

Labeled checkbox. A sunken white box that draws the shell check glyph when ticked, label to the right, optional hint below.

04/Machine-first documentation

Every component ships a typed contract next to it — a {Name}.doc.ts file that a person or a model can read to use the component correctly without opening the implementation. The schema is a TypeScript interface, so a doc that type-checks is, by construction, complete: it can’t omit the parts a reader needs.

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.

Worked example · Button

The system's one interactive control. Square, bevelled, uppercase. Renders as an anchor when `href` is set, a native button otherwise.

PC · Windows 98 · 1998
Mac · Mac OS 8 Platinum · 1997

Do

  • Reserve the accent variant for the one primary action in a view.
  • Write labels that name the action ("See the work", "Get in touch"), not "Click here".
  • Set `href` when the control navigates — it renders a real <a> with correct semantics.

Don’t

  • Stack two accent buttons side by side; it flattens the hierarchy.
  • Round the corners or add a shadow — the button is square, and its depth is the bevel.
PropTypeDefaultDescription
variant'solid' | 'accent' | 'outline' | 'ghost''solid'Visual weight. solid is the standard push button; accent fills with the selection colour; outline is a thin-bevelled toolbar button; ghost is flat until hover.
size'sm' | 'md''md'Padding and type scale.
hrefstringWhen provided, renders an <a> instead of a <button>.
disabledbooleanfalseDisables the native button.
children *ReactNodeButton label.
{ } Button’s full machine-readable spec
{
  "name": "Button",
  "category": "Action",
  "description": "The system's one interactive control. Square, bevelled, uppercase. Renders as an anchor when `href` is set, a native button otherwise.",
  "keywords": [
    "button",
    "cta",
    "action",
    "link",
    "submit"
  ],
  "usage": {
    "description": "Use Button for the actions a view offers — submitting a form, sending a message, moving to the next project. Reach for the accent variant only for the single most important action on the screen; everything else is solid, outline, or ghost by descending emphasis.",
    "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": "Round the corners or add a shadow — the button is square, and its depth is the bevel."
      }
    ],
    "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. solid is the standard push button; accent fills with the selection colour; outline is a thin-bevelled toolbar button; ghost is flat until hover.",
      "default": "'solid'"
    },
    {
      "name": "size",
      "type": "'sm' | 'md'",
      "description": "Padding and type scale.",
      "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": "Accent",
      "code": "<Button variant=\"accent\">Get in touch</Button>"
    },
    {
      "label": "Outline, small",
      "code": "<Button variant=\"outline\" size=\"sm\">Résumé ↓</Button>"
    },
    {
      "label": "As link",
      "code": "<Button variant=\"ghost\" href=\"/work/\">All work →</Button>"
    }
  ],
  "accessibility": [
    "Renders a native <button> or <a> — full keyboard and screen-reader support for free.",
    "Focus draws the skin-bound outline — the PC dotted rectangle, the Mac solid ring — inside the button edge, so it never disturbs the bevel.",
    "Disabled state sets the native disabled attribute, not just styling."
  ],
  "theming": {
    "vars": [
      {
        "name": "--color-accent",
        "description": "Fill of the accent variant and the solid hover.",
        "default": "#000080"
      },
      {
        "name": "--color-fg",
        "description": "Fill of the solid variant; text of outline/ghost.",
        "default": "#000000"
      },
      {
        "name": "--duration-fast",
        "description": "Speed of the colour transition on hover/focus.",
        "default": "150ms"
      }
    ]
  }
}

The other 13 components carry the same contract — props, examples, accessibility, and theming vars — each rendered live under both skins at its own source.

05/Principles · 5

The rules BONES runs on: two tiers, semantic tokens over raw values, two machine skins, and a system legible to people and models at once.

Two tiers, and only two

Every 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, bevel, spacing, a type role.

Semantic tokens, never raw values

A component that names colours or measurements by appearance is a component that breaks the next time the theme, the palette, or the scale moves. Name by purpose instead and the code stays true across both schemes for free.

The skin changes shape, not just colour

A theme that only repaints is a palette. This one re-binds geometry too: radius-control is 0 on the pc and 6px on the mac, so the same Button is a hard Windows rectangle on one machine and a rounded Platinum rect on the other. Focus style, the shell font, and the title-bar pattern move with it. Components never branch on the skin — they read rounded-control and let the token decide.

One face, at the sizes the shell used

The interface is set in a single face — --font-mono, which resolves to Tahoma on the pc and Charcoal/Geneva on the mac — from fonts already installed, so no webfont is downloaded either way. Weight does the work a second family used to: regular for everything, bold for a heading or a title bar. There is no in-between weight, because neither shell had one.

Legible to people and machines

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


Role
Design & Engineering

Year
2026

Disciplines
Design tokensDocumentationBuild toolingMeta