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.
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
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
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
| Field | Type | What it carries |
|---|---|---|
| name | string | The exported component name, unique across the system. |
| category | ComponentCategory | Its slot in the mental model — drives grouping. |
| description | string | One-line summary, used in listings and headings. |
| keywords | string[] | Search terms and synonyms an agent might try. |
| usage | UsageDoc | The narrative: fuller description, do/don’t guidance, anatomy. |
| props | PropDoc[] | Every public prop — name, type, default, whether required. |
| examples | ExampleDoc[] | Copy-pasteable JSX, simplest first. |
| accessibility | string[] | Keyboard, focus, and screen-reader guarantees. |
| theming | ThemingDoc? | 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.
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.
| Prop | Type | Default | Description |
|---|---|---|---|
| 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. |
| href | string | — | When provided, renders an <a> instead of a <button>. |
| disabled | boolean | false | Disables 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, 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.