Stop fighting your agent.Give it product-ready code,not a prompt.

nanaOS gives solo builders product-ready code design: versioned components, semantic tokens, and contracts an agent can read. Set one brand seed, then build against the same rules the package ships.

See the build pathTry your brand
One authored source generates a catalog for people, contracts for agents, and runtime packages for products.Authored truthtokens + SCSSrecipe.tscanonCatalogfor peopleContractsfor agents</>CSS + Web Componentsworks anywhereReacttyped props
The solo-builder path at a glanceInstall once. Give the agent rules. Brand through one controlled entry point.
01Install the whole systemOne package brings the runtime, typed components, patterns, contracts, fonts, icons, and CLI.
02Give the agent rulesThe installed contract replaces repeated screenshot handoffs with versioned props, values, and audits.
03Brand through one seedBrand roles follow the seed; status, data, body surfaces, and supplied artwork stay protected.
Generic agentRepeat context, screenshots, and visual QA for each surface.nanaOSReuse one versioned contract across screens, products, and updates.

Playground

ChipComponent · Input
ComboboxComponent · Input
DialogComponent · Overlay
DrawerComponent · Overlay
Hover CardComponent · Overlay
SidebarComponent · Navigation
Table of ContentsComponent · Navigation
TabsComponent · Navigation
TextareaComponent · Input
ToastComponent · Feedback
TooltipComponent · Feedback
Search DialogPattern · Search
01 · Philosophy

One seed color. Build anything you can name.

People explore the catalog. Agents read versioned contracts. Products install the runtime package. The three stay aligned by version, not by memory.

Authored truth5 principles

Design system principles

  1. Consistency
  2. Scalable
  3. Maintainable
  4. DRY (Don't Repeat Yourself)
  5. Agent-friendly
  1. Catalogfor people
  2. Contractsfor agents
  3. CSS + Web Componentsworks anywhere
  4. Reacttyped props
  5. Agent gatewaymachine-readable
Five design system principlesOne authored language for humans and agents.
01

Consistency

One intent keeps one semantic name across themes, design, components, and product UI.

02

Scalable

Measured primitives and stable semantic aliases grow without re-ranking the system.

03

Maintainable

Every decision has one owned source, with generated surfaces derived from it.

04

DRY

Tokens, recipes, contracts, previews, and adapters are reused or generated instead of copied.

05

Agent-friendly

Public contracts and semantic APIs make intent legible to people and agents without reverse-engineering pixels.

02 · Start

Start with product-ready code. Make it unmistakably yours.

nanaOS is not a folder of snippets to copy. One version gives a solo builder 51 typed components, 11 patterns, 347 tokens, and machine-readable contracts. The reference consumer builds, typechecks, and passes the strict audit; your product stays yours through public props and semantic brand inputs instead of a component fork.

A planning model for a solo builder

The gap is not per screen. It is per change.

Building the first version of a screen is the cheap part. What costs a solo builder their year is that every screen keeps its own private copy of every decision — so the fifth change costs more than the first, and the forty-eighth screen costs more than the third.

Screens
System-wide changes over the product’s life

Showing a 24-screen responsive web or app flow with 8 system-wide changes — a rebrand, a shape decision, a density pass, a contrast fix. Drag the chart, or arrow through it, to move between scenarios.

336 interface decisions for 24 screens, at an assumed 14 per screen. Left of the field each one is a new judgement call, and the drift grows with the surface. Right of it each one lands on a size step, a token colour, and a spacing row the package already ships.

At 24 screens and 8 system-wide changes, the model puts the two paths here:

No system
176 h
nanaOS
50 h
Hours you never spend
126 h3.5×

That is 3.1 working weeks at 40 hours — the same product, built once instead of re-decided every time it changes.

0h90h180h270h112243648Screens shipped

Now change one thing, and count the places you have to touch.

Not a new feature — a decision you already made and want to revise. Pick one:

Every gap, pad and inset moves together. There is no single rule to change — spacing is set on each element.

Hand-rolled
grows with N

1,031

places to find, edit, and re-check across 24 screens

nanaOS
flat in N

48

token definitions — the same number at 3 screens or 48

21× fewer places to be wrong — and inside the package those 48 definitions reach 1,797 declarations across all 51 components on their own, a 37× fan-out that is counted, not modelled.

And the sweep has to be perfect. At a forgiving one miss in two hundred, 1,031 hand edits carry 5.2 expected misses — roughly a 1 in 176 chance of landing clean. The 48-token version has one property the sweep can never have: the audit proves the set is complete. audit:hardcoded-values reports zero hardcoded design values in the authored source, so there is nowhere else the value could be hiding.

The nanaOS column and the declaration counts are literal: token definitions in the published contract, and var(--token) references in dist/nanaos.css. The hand-rolled column is the one modelled figure, and the model is deliberately generous to it: this system’s own density of 35.2 declarations per component for this change, across the 29 distinctly-styled units a 24-screen product ends up with once component vocabulary saturates — not across 7×24 unshared copies.

One seed. 10,472 declarations already wired to it.

The shipped stylesheet never writes a colour, a radius, or a step of spacing directly — it writes var(--token) 10,472 times. That is why a system change is one edit instead of a sweep: the wiring is the product. 448 foreground × background combinations are re-measured on every release, and 35 checks have to pass before a version ships. Your product gets its own gate: the package ships a CLI, and nanaos audit . --strict holds your code to the same contract.

2,705

decisions already taken, versioned, and re-checked on every release — before you write a line of product UI. Each number below is counted from the published package, not estimated.

473
Typed propsAcross all 51 components, each one described in contracts/components.json.
566
Enumerated valuesA closed set your agent chooses from instead of inventing one.
260
Documented statesHover, focus, checked, disabled, invalid — specified, not improvised.
108
Named slotsComposition points that stay stable across versions.
445
Machine-readable constraintsRules describing what a component may not be asked to do.
254
ARIA requirementsShipped with the components, on all 51 of them.
137
Keyboard bindingsAlready wired, already tested, already the ones people expect.
349
Design tokensOne vocabulary for colour, space, type, shape, and motion.
113
Recorded decisionsADRs saying why the system is this way, and what was rejected.
The full model, dimension by dimension
Illustrative planning model — not a benchmarkDirectional ranges for planning conversations, not measured product or model performance.
DimensionNo systemnanaOS
WorkflowScreenshot by screenshot; repeat the handoff for each view, then reconcile them by eye.Intent → contract → composition → evidence, then reuse the path.
First usable pass94 hscreens × 3 h + 0.1 h drift per prior screen47 h4 h setup + screens × 1.8 h
8 system-wide changes82 hchanges × styled units × 0.35 h3 hchanges × 0.4 h
Places reopened per change29 styled unitsgrows with the surface1 set of token definitionsflat in the surface
Context the agent re-derives604.8k–907.2k tokensscreens × (1 + changes) × 2.8k–4.2k28.8k–43.2k tokensscreens × 1.2k–1.8k, read once
Total176 hbuild + change50 hbuild + change

Workflow

No system
Screenshot by screenshot; repeat the handoff for each view, then reconcile them by eye.
nanaOS
Intent → contract → composition → evidence, then reuse the path.

First usable pass

No system
94 hscreens × 3 h + 0.1 h drift per prior screen
nanaOS
47 h4 h setup + screens × 1.8 h

8 system-wide changes

No system
82 hchanges × styled units × 0.35 h
nanaOS
3 hchanges × 0.4 h

Places reopened per change

No system
29 styled unitsgrows with the surface
nanaOS
1 set of token definitionsflat in the surface

Context the agent re-derives

No system
604.8k–907.2k tokensscreens × (1 + changes) × 2.8k–4.2k
nanaOS
28.8k–43.2k tokensscreens × 1.2k–1.8k, read once

Total

No system
176 hbuild + change
nanaOS
50 hbuild + change
  • Build: screens × 3 h with no system, plus 0.08 h for every screen already shipped — the cost of re-reading what you did last time and matching it. nanaOS is 4 h of setup plus screens × 1.8 h, with no drift term, because the contract does the reconciling.
  • Change: one system-wide change costs 0.35 h per distinctly-styled unit with no system. The unit count is deliberately generous to the hand-rolled path — it saturates at 7 × (1 + ln N) rather than growing with every screen, because real hand-rolled code does share components. With nanaOS it is 0.4 h — one remapped set of tokens and one audit pass — at any screen count.
  • Context: screens × 2.8k–4.2k planning tokens with no system, re-derived on every change; or screens × 1.2k–1.8k when the agent reads the relevant contract once instead of repeating the visual context. This means planning context, not API billing.
  • Decisions per screen: the field above assumes 14 interface decisions per screen — sizes, states, spacings, tones — only to give the particle count a scale.

These are explicit planning assumptions only. They are not a benchmark, guarantee, or claim about measured agent output. What is not an assumption is the shape: without a shared system the cost of a change scales with the number of screens; with one, it does not.

The hour coefficients are conservative placeholders for a small product surface, and every one of them is written out above. The package counts — 2,705 decisions, 10,472 token references, 448 contrast combinations — are counted from the published package, not estimated.

Four ownership categories. One controlled seed.

Where your brand belongs

Use direct brand roles where nanaOS already publishes them, and opt into action mappings in your product theme. Never let the seed silently replace semantic meaning. These previews share the live seed from the studio below; protected surfaces stay unchanged when you move it.

Paths from one seed to a package state or product surface

  1. 01SeedOne approved source color.
  2. 02RampMapped light, dark, and tint stops.
  3. 03Semantic role--brand-primary directly, or an explicit product mapping into --action-primary-bg.
  4. 04Component or surfacePublic API or product composition owns behavior and states.

seedrampsemantic rolepublic surface

Package default: system actions stay blue. Starter opt-in: the generated product theme maps those action roles to your seed.

Keep the brand kit small and explicit

Collect identity inputs once. Let semantic roles, public component axes, and product-owned compositions do the distribution work.

  • Seed colorLive studioOne approved source creates the brand ramp and emphasis roles.
  • Radius presetLive studioRemap the existing shape scale without inventing component CSS.
  • UI typefaceProduct layerSet the readable interface family; reserve display type for bounded marketing surfaces.
  • Logo & artworkAsset onlySupply the original files. The seed never recolors source artwork.
  • Light & darkPaired rolesGenerate both modes from the same intent and validate each contrast pairing.
  • DensityPublic axisChoose existing component density values; do not make spacing part of the color seed.
Inspect brand ownership

Inspecting Direct brand states. The other boundaries remain visible for comparison.

Direct brand states

Built in

Built in

These published component and pattern states read --brand-* roles directly. Changing the brand ramp reaches them without remapping action semantics.

  • Primary Badge--brand-primary
  • Selected Chip--brand-primary / --brand-primary-dim1
  • Checked Cardbrand checkmark; outlined / filled also use brand border or tint
  • Sidebar brand mark / avatar--brand-primary / --brand-active-background
  • Table of Contents — appearance=brand--brand-hover-background / --brand-active-background
  • Link active state--brand-primary
  • Pattern accentsContent Layout prose blockquote / Search Dialog match / Footer link active

Mapped actions

Opt-in mapping

Opt-in mapping

The starter theme maps the seed into action roles inside @layer nanaos.product. Package defaults stay system blue until you opt in.

  • Primary Button--action-primary-bg*
  • Checked Checkbox / Radio / Switch--action-primary-bg*
  • Link default / hover--action-link-fg*

Product-owned accents

Product decision

Product decision

These are not published package roles; compose them intentionally in product CSS or request a public recipe/API.

  • Header accent wrapperproduct composition
  • Marketing heroproduct composition
  • Accent Tagpublic recipe / API required

Protected semantics

Protected

Protected

Keep meaning, legibility, data interpretation, and source artwork independent of the seed.

  • Selected Tabs--background-default (grouped) / --background-dim2 (standalone)
  • Progress--status-success
  • Body text / background--foreground-default / --background-default
  • Success / warning / error--status-success / --status-warning / --status-error
  • Destructive action--action-danger-bg
  • Data visualizationproduct-owned data palette
  • Supplied source assetsource asset
Representative surface previewDirect brand states#D92798
Primary Badge — direct role
Selected Chip — direct role
Selected Card — outlined / filled onlyStarter kitThe selected border and tint read brand roles directly.

Built in means the package reads a brand role directly. Opt-in mapping is written by the generated product theme. Product-owned accents need an intentional composition or public API. Protected roles keep meaning, contrast, data, and supplied artwork intact.

A package, not a pile of copies

Copy-paste libraries hand you source. From the moment it lands in your repository, your copy and ours are two different programs — a fix we make never reaches you, and the variant you need means copying again. nanaOS keeps the components inside the package, so the variant is already there behind a prop and the fix arrives with an update.

Copy-paste library
  1. v0.6.0
  2. v0.6.1
  3. v0.7.0
Your repositoryComponent source lives here now. It is yours to maintain, and it stops at v0.6.0.
nanaOS
  1. v0.6.0
  2. v0.6.1
  3. v0.7.0
Your productThe components stay in the package. Every release reaches you through npm update @nanaorbit/nanaos.
A copy is a fork the moment it landsThe difference is not style. It is whether a fix we make in the system can still reach your product.
QuestionCopy-paste librarynanaOS
Adding a variantCopy the component again and edit the copyAlready there — change one prop
When we improve itYour copy never hears about itnpm update @nanaorbit/nanaos, and it arrives
Who owns the sourceYou do, including every bug in itnanaOS maintainers, under a published version contract
What your agent readsWhatever you copied, whenever you copied itcontracts/*.json for the exact version installed

Get the whole system, not one component at a time

There is no per-component download and no starter to clone. Five steps put the entire system in your project: one CSS import, then a generated theme and verification gate.

01

Ask for access

Repository Read on nanaorbit/nanaOS, and a classic personal access token carrying read:packages. GitHub Packages does not accept fine-grained tokens, and Read is the whole permission you need — nothing above it.

export GITHUB_PACKAGES_READ_TOKEN=<your classic PAT>
02

Point the scope at the registry

One .npmrc in the project, with the token read from the environment. Add .npmrc to .gitignore — the credential never enters source control.

# .npmrc @nanaorbit:registry=https://npm.pkg.github.com //npm.pkg.github.com/:_authToken=${GITHUB_PACKAGES_READ_TOKEN}
03

Install once

One version number covers the CSS, the React adapters, the Web Components, the Patterns, the contracts, the fonts, the icons, and the CLI.

npm install @nanaorbit/nanaos
04

Import the CSS once

The only nanaOS import your product makes. Every component's styles arrive with it; there is no per-component import list to keep in sync.

@import "@nanaorbit/nanaos/css";
05

Generate the theme, then verify

Create the product theme once, check the installation, and make the strict audit a build gate. Your agent gets a file-and-line result instead of guessing from screenshots.

npx --package=@nanaorbit/nanaos nanaos init theme npx --package=@nanaorbit/nanaos nanaos doctor . npx --package=@nanaorbit/nanaos nanaos audit . --strict
node_modules/@nanaorbit/nanaosv0.6.0
  1. dist/nanaos.cssEvery component, both themes, 347 tokens
  2. react/51 typed adapters
  3. web-components/The same components as n-* elements
  4. css/patterns11 composed patterns
  5. contracts/2.1 MB your agent reads
  6. fonts/, icons/Geist and the Material Symbols subset
  7. bin/nanaosdoctor, audit, migrate, init theme
  8. ADOPTION.mdWhich component, when — readable from node_modules
One install, one version numberNothing is copied into your repository, and nothing here needs a second package to work.

Your agent reads the same system you do

There is no separate nanaOS documentation to keep in sync with the code. The package carries its own manual: ADOPTION.md states which component fits which intent, and contracts/*.json states every component, prop, and allowed value — for the exact version you installed. Keep one canonical instruction file in your repository: AGENTS.md. If you add a nanaOS.md, make it a short pointer to that file rather than a second copy that can drift.

People read the catalogRendered previews, live playgrounds, anatomy. Hosted on the web, never bundled into your project.
Agents read the contracts2.1 MB of JSON in node_modules. Offline, versioned with the package, and checked by the strict audit.
Two readers, one source. Neither needs the other to be re-explained.
AGENTS.mdcanonical
# Agent instructions UI in this project is built with nanaOS (`@nanaorbit/nanaos`). Before writing any UI: 1. Read `node_modules/@nanaorbit/nanaos/ADOPTION.md` — the which-component-when rule. 2. Read `node_modules/@nanaorbit/nanaos/contracts/components.json` — every component, every prop, every allowed value. Never invent a prop or a value. 3. Import from `@nanaorbit/nanaos/react`. Never copy component source, and never reproduce a component root class in markup. 4. Use semantic tokens only — `--foreground-*`, `--background-*`, `--spacing-*`, `--border-radius-*`. No raw hex, no raw px, no primitives. 5. Product CSS goes in `app/nanaos-theme.css`, inside `@layer nanaos.product`, and nowhere else. The work is not done until `npx --package=@nanaorbit/nanaos nanaos audit . --strict` is clean.
nanaOS.mdoptional pointer
# nanaOS Canonical UI instructions live in [AGENTS.md](./AGENTS.md). Before UI work, read: - `node_modules/@nanaorbit/nanaos/ADOPTION.md` - `node_modules/@nanaorbit/nanaos/contracts/components.json` Do not duplicate the component rules here. Keeping this file as a pointer lets AGENTS.md stay the one project-owned source that every coding agent reads.

The instructions are enforced, not trusted

npx --package=@nanaorbit/nanaos nanaos audit . --strict checks the built project against the same contracts — setup, attributes, props, enum values, token boundaries, component selector overrides, and hand-rolled replicas of nanaOS roots — and reports the file and the line. Run it in CI and an agent cannot quietly drift.

Your brand enters at one point

You do not hand-pick nine brand values and hope they hold up. You pick one seed. nanaOS derives the ramp from it — every stop mapped to stay inside sRGB so the browser never clips it, every pairing scored against APCA — and writes the result into one file in your project. Change the seed below and watch the components follow.

The package reads --brand-* directly only in published brand states. This starter then opts in to a second lane by mapping primary actions and links inside @layer nanaos.product. Without that file, package actions stay system blue.

Seed color
#D92798oklch(60.02% 0.2302 347.94)
Or start from a preset
Derived ramp
  1. #B1257CHover, light themechroma mapped
  2. #D92798Seed — brand primary
  3. #F679BEHover, dark themechroma mapped
  4. #F89DCCBrand primary, dark themechroma mapped
  5. #FFFAFCSoftest tintchroma mapped
APCA checks — all clear
  • Primary action, light themeLc 82.2
  • Primary action, dark themeLc 61.6
  • Link on light backgroundLc 81.5
  • Link on dark backgroundLc 61.7
Real components, your seed
PublishPreviewLearn more
Selected planA checked Card takes its border and tint from the brand.
Brand tint surface
  • --brand-primaryPrimary Badge, selected Chip, checked Card
  • --brand-primary-dim1Selected Chip surface
  • --action-primary-bgPrimary Button and checked Checkbox, Radio, Switch
  • --action-link-fgLink default and hover states
app/nanaos-theme.cssgenerated from your seed
/* nanaos-theme.css — derived from one seed. */ /* seed: oklch(60.02% 0.2302 347.94) */ @import "@nanaorbit/nanaos/css"; @layer nanaos.product { :root { --brand-primary: light-dark(oklch(60.02% 0.2302 347.94), oklch(80% 0.1224 347.94)); --brand-secondary: light-dark(oklch(52% 0.1914 347.94), oklch(74% 0.1703 347.94)); --brand-primary-dim1: light-dark(oklch(60.02% 0.2302 347.94 / 12%), oklch(80% 0.1224 347.94 / 10%)); --brand-primary-dim2: light-dark(oklch(60.02% 0.2302 347.94 / 2%), oklch(80% 0.1224 347.94 / 6%)); --brand-hover-background: light-dark(oklch(60.02% 0.2302 347.94 / 2%), oklch(80% 0.1224 347.94 / 6%)); --brand-hover-border: light-dark(oklch(60.02% 0.2302 347.94), oklch(80% 0.1224 347.94)); --brand-active-background: light-dark(oklch(80% 0.1224 347.94 / 6%), oklch(80% 0.1224 347.94 / 10%)); --action-primary-bg: light-dark(oklch(54% 0.1987 347.94), oklch(79% 0.1299 347.94)); --action-primary-bg-hover: light-dark(oklch(63% 0.2302 347.94), oklch(88% 0.0676 347.94)); --action-primary-bg-active: light-dark(oklch(44% 0.162 347.94), oklch(69% 0.2158 347.94)); --action-link-fg: light-dark(oklch(50% 0.184 347.94), oklch(79% 0.1299 347.94)); --action-link-fg-hover: light-dark(oklch(42% 0.1547 347.94), oklch(88% 0.0676 347.94)); } }

Save it as app/nanaos-theme.css and import it after the nanaOS CSS. Everything in it sits inside @layer nanaos.product, which wins over every nanaOS layer by cascade precedence — no !important, no specificity fight, and no forked component anywhere in your repository.

Roundness is a remap, not a rewrite

Shape works the same way as color. You never invent 5px; you point an existing step at another existing step and the whole system follows. Three values never move — --border-radius-0, --border-radius-800, and --border-radius-circular are shape sentinels, not scale steps. Watch the avatar: it stays a circle in every preset, because a sharp brand should not square someone’s face.

The scale as nanaOS ships it. Nothing is remapped.

/* Default — nothing is remapped. This is the scale nanaOS ships. */
Real components, your scale
ContinueCancel
CardReads --border-radius-16.
Tag
--border-radius-circularPill--border-radius-800--border-radius-0

Three ways to change something, in the order to try them

Every request to make nanaOS look different lands on one of three rungs. The higher the rung, the more common it is and the less it costs you later.

  1. Common first move

    Choose a published axis value

    The behavior already exists. Choosing data-scroll="retract" is immediate, costs no new component CSS, and stays upgrade-safe. Retract still needs the product to observe scroll direction and toggle data-retracted — that runtime responsibility is explicit, not hidden.

    <header class="header" data-scroll="retract" data-background> <!-- Brand, navigation, and actions --> </header>
  2. Brand setup

    Override a semantic token

    Brand color, roundness, typeface. In your file, inside @layer nanaos.product — which wins over every nanaOS layer by cascade precedence. No !important, no specificity fight, no fork.

    @layer nanaos.product { :root { --brand-primary: light-dark(oklch(60% 0.23 348), oklch(80% 0.15 348)); } }
  3. For product-specific needs

    Build on the tokens

    A chart, a timeline, something only your product needs. Build it in your repository, on nanaOS semantic tokens only. It inherits the system without becoming part of it.

    .revenue-sparkline { gap: var(--spacing-8); border-radius: var(--border-radius-12); color: var(--foreground-dim1); }

And three things that are never the answer

  • Copying component source into your repoadoption/manual-component-root — critical
  • Overriding component selectors like .button__iconcomponents/selector-override — critical
  • Reaching for primitives like --neutral-50 or --blue-54tokens/primitive-access — critical

All three fail npx --package=@nanaorbit/nanaos nanaos audit . --strict with the file and the line, before the change can reach production. If the gap is real and systemic, ask upstream instead — a fix in the system ships to every product using it, including yours.

03 · Adoption

Start clean or migrate with evidence.

The package carries the runtime, prop contracts, framework adapters, migration report, and strict consumer audit needed to keep unsupported UI out of a product.

01

Install one package

Runtime CSS, adapters, contracts, patterns, fonts, icons, and the CLI share one version.

npm install @nanaorbit/nanaos
02

Choose one runtime

React apps use generated client adapters. Other stacks register the same n-* Web Components.

import { Button } from "@nanaorbit/nanaos/react";
03

Theme through semantics

Product identity enters through the nanaos.product layer without forking component CSS.

npx --package=@nanaorbit/nanaos nanaos init theme
04

Gate every migration

Strict audits turn unsupported UI into file-and-line remediation instead of visual guesswork.

npx --package=@nanaorbit/nanaos nanaos migrate adopt . npx --package=@nanaorbit/nanaos nanaos audit . --strict
One system · two fluent readers

Beautiful enough to explore. Explicit enough to migrate.

nanaOS turns design intent into an installable, testable operating system for product UI.

Open the PlaygroundView source
On this pageTable of Contents
Table of ContentsPlaygroundWhy nanaOSStart & brandAdoption