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.
Playground
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.
Design system principles
- Consistency
- Scalable
- Maintainable
- DRY (Don't Repeat Yourself)
- Agent-friendly
- Catalogfor people
- Contractsfor agents
- CSS + Web Componentsworks anywhere
- Reacttyped props
- Agent gatewaymachine-readable
Consistency
One intent keeps one semantic name across themes, design, components, and product UI.
Scalable
Measured primitives and stable semantic aliases grow without re-ranking the system.
Maintainable
Every decision has one owned source, with generated surfaces derived from it.
DRY
Tokens, recipes, contracts, previews, and adapters are reused or generated instead of copied.
Agent-friendly
Public contracts and semantic APIs make intent legible to people and agents without reverse-engineering pixels.
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.
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.
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
1,031
places to find, edit, and re-check across 24 screens
nanaOS
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
| Dimension | No system | nanaOS |
|---|---|---|
| Workflow | Screenshot by screenshot; repeat the handoff for each view, then reconcile them by eye. | Intent → contract → composition → evidence, then reuse the path. |
| First usable pass | 94 hscreens × 3 h + 0.1 h drift per prior screen | 47 h4 h setup + screens × 1.8 h |
| 8 system-wide changes | 82 hchanges × styled units × 0.35 h | 3 hchanges × 0.4 h |
| Places reopened per change | 29 styled unitsgrows with the surface | 1 set of token definitionsflat in the surface |
| Context the agent re-derives | 604.8k–907.2k tokensscreens × (1 + changes) × 2.8k–4.2k | 28.8k–43.2k tokensscreens × 1.2k–1.8k, read once |
| Total | 176 hbuild + change | 50 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 h
screens × 3 h + 0.1 h drift per prior screen - nanaOS
- 47 h
4 h setup + screens × 1.8 h
8 system-wide changes
- No system
- 82 h
changes × styled units × 0.35 h - nanaOS
- 3 h
changes × 0.4 h
Places reopened per change
- No system
- 29 styled units
grows with the surface - nanaOS
- 1 set of token definitions
flat in the surface
Context the agent re-derives
- No system
- 604.8k–907.2k tokens
screens × (1 + changes) × 2.8k–4.2k - nanaOS
- 28.8k–43.2k tokens
screens × 1.2k–1.8k, read once
Total
- No system
- 176 h
build + change - nanaOS
- 50 h
build + change
- Build: screens ×
3 hwith no system, plus0.08 hfor every screen already shipped — the cost of re-reading what you did last time and matching it. nanaOS is4 hof setup plus screens ×1.8 h, with no drift term, because the contract does the reconciling. - Change: one system-wide change costs
0.35 hper distinctly-styled unit with no system. The unit count is deliberately generous to the hand-rolled path — it saturates at7 × (1 + ln N)rather than growing with every screen, because real hand-rolled code does share components. With nanaOS it is0.4 h— one remapped set of tokens and one audit pass — at any screen count. - Context: screens ×
2.8k–4.2kplanning tokens with no system, re-derived on every change; or screens ×1.2k–1.8kwhen 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
14interface 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
- 01SeedOne approved source color.
- 02RampMapped light, dark, and tint stops.
- 03Semantic role
--brand-primarydirectly, or an explicit product mapping into--action-primary-bg. - 04Component or surfacePublic API or product composition owns behavior and states.
seed
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 color
Live studio One approved source creates the brand ramp and emphasis roles. - Radius preset
Live studio Remap the existing shape scale without inventing component CSS. - UI typeface
Product layer Set the readable interface family; reserve display type for bounded marketing surfaces. - Logo & artwork
Asset only Supply the original files. The seed never recolors source artwork. - Light & dark
Paired roles Generate both modes from the same intent and validate each contrast pairing. - Density
Public axis Choose existing component density values; do not make spacing part of the color seed.
Inspecting Direct brand states. The other boundaries remain visible for comparison.
Direct brand states
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 Card
brand 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 accents
Content Layout prose blockquote / Search Dialog match / Footer link active
Mapped actions
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
These are not published package roles; compose them intentionally in product CSS or request a public recipe/API.
- Header accent wrapper
product composition - Marketing hero
product composition - Accent Tag
public recipe / API required
Protected semantics
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 visualization
product-owned data palette - Supplied source asset
source asset
#D92798Built 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.
- v0.6.0
- v0.6.1
- v0.7.0
- v0.6.0
- v0.6.1
- v0.7.0
npm update @nanaorbit/nanaos.| Question | Copy-paste library | nanaOS |
|---|---|---|
| Adding a variant | Copy the component again and edit the copy | Already there — change one prop |
| When we improve it | Your copy never hears about it | npm update @nanaorbit/nanaos, and it arrives |
| Who owns the source | You do, including every bug in it | nanaOS maintainers, under a published version contract |
| What your agent reads | Whatever you copied, whenever you copied it | contracts/*.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.
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.
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.
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.
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.
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.
- dist/nanaos.cssEvery component, both themes, 347 tokens
- react/51 typed adapters
- web-components/The same components as n-* elements
- css/patterns11 composed patterns
- contracts/2.1 MB your agent reads
- fonts/, icons/Geist and the Material Symbols subset
- bin/nanaosdoctor, audit, migrate, init theme
- ADOPTION.mdWhich component, when — readable from node_modules
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.
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.
- #B1257CHover, light theme
chroma mapped - #D92798Seed — brand primary
- #F679BEHover, dark theme
chroma mapped - #F89DCCBrand primary, dark theme
chroma mapped - #FFFAFCSoftest tint
chroma mapped
Primary action, light themeLc 82.2 Primary action, dark themeLc 61.6 Link on light backgroundLc 81.5 Link on dark backgroundLc 61.7
--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
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.
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.
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> 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)); } } 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.
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.
Install one package
Runtime CSS, adapters, contracts, patterns, fonts, icons, and the CLI share one version.
Choose one runtime
React apps use generated client adapters. Other stacks register the same n-* Web Components.
Theme through semantics
Product identity enters through the nanaos.product layer without forking component CSS.
Gate every migration
Strict audits turn unsupported UI into file-and-line remediation instead of visual guesswork.
Beautiful enough to explore. Explicit enough to migrate.
nanaOS turns design intent into an installable, testable operating system for product UI.