---
title: Introducing the YPAI Design System
url: https://ypai.ai/blog/infrastructure/announcing-ypai-design-system/
category: Sovereign Infrastructure
published: 2026-05-12T00:00:00.000Z
modified: 2026-05-12T00:00:00.000Z
author: Henrik Roine
readingTime: 8
tags: [Design System, Frontend, Astro, Accessibility]
---

# Introducing the YPAI Design System

> An 8-week sprint to ship a public reference design system — what we built, what we learned, what's open.

Today we are tagging **v1.0.0** of the YPAI Design System and opening its reference site to the public at [`/design/`](/design/). Eight weeks ago, the front end of `yourpersonalai.net` was a perfectly functional Astro app that had grown the way most marketing sites grow: page by page, designer by designer, opinion by opinion. The system worked. The system was also a fan-out of bespoke components held together by tribal knowledge. This post explains what we changed, why, and what we are not yet shipping.

## Why we built it

The trigger was an audit, not a vision. In late February we ran a sweep of every CSS custom property declared anywhere under `src/` and got a number that surprised us.



The fix could not be "rewrite everything," because the site was healthy and shipping revenue. It also could not be "add a new token file," because we already had six. What we needed was a single layer of canonical tokens with a documented contract, a codemod that migrated the existing 499 files to that layer, and a reference site that made the contract findable a year later when whoever-takes-over is reading the code at 11pm. Eight weeks, one engineer plus AI agents, no rewrite. That was the brief.

## The five ideas that made it work

A design system is not a component library. We kept reminding ourselves of this throughout the sprint, because there is enormous gravity toward "Storybook full of widgets" as the deliverable. The widgets are the easy part. The five ideas below are what actually distinguishes a system that *holds* from a folder of `.astro` files.

### Tokens, not values

Every spacing, radius, shadow, z-index and color in `src/` reads from a `--ds-*` custom property. The codemod replaced 8,814 individual literals across 499 files; the CI lint blocks new ones. Magic numbers in components are a bug class, not a style preference. When a designer asks "can we make this 6px bigger?", the answer is "we adjust the token; the 47 places it appears adjust with it."



### Hub-tinted identity

YPAI's blog has six content hubs — compliance, infrastructure, data engineering, agentic AI, industry solutions, research — and each one carries its own accent color. The accent is exposed as `--hub-accent` and the design system reads it via `--ds-color-accent`, which means a single `



### AI-citable content layer

A design system in 2026 is not done when it looks good in Chrome. It has to be legible to the systems that *quote* you back to your prospective customer. Every article on `yourpersonalai.net` ships with `schema.org/Dataset` JSON-LD, a `/llms.txt` route summarising the canonical pages, a `/article-md/<slug>/` Markdown twin for AI ingestion, a Pagefind index that runs client-side with no API key, and per-route OG images generated at build[^3]. The design system extends to *how AI systems read us*, not just how humans see us.

## What's actually shipped

The reference site at [`/design/`](/design/) is now public. Concretely:

- [`/design/`](/design/) — overview + the four principles, with live token previews
- [`/design/tokens/`](/design/tokens/) — every `--ds-*` token rendered as a swatch or spec
- [`/design/motion/`](/design/motion/) — duration/easing scale + the `data-motion` contract
- [`/design/components/`](/design/components/) — index of all 9 primitives
- [`/design/components/icon/`](/design/components/icon/) — `lucide-static`-backed, tree-shaken
- [`/design/components/input/`](/design/components/input/) — text input + validation states
- [`/design/components/select/`](/design/components/select/) — accessible custom select with keyboard nav
- [`/design/components/tabs/`](/design/components/tabs/) — including the `data-tab-panel` workaround for dynamic slots
- [`/design/components/tooltip/`](/design/components/tooltip/) — Floating UI positioning, ARIA correct
- [`/design/components/dialog/`](/design/components/dialog/) — `<dialog>` element, focus trap, restore-focus on close
- [`/design/components/toast/`](/design/components/toast/) — token-aware z-index, motion-respecting
- [`/design/components/skeleton/`](/design/components/skeleton/) — shimmer that disappears under reduced-motion
- [`/design/components/footnote/`](/design/components/footnote/) — the component you're reading right now
- [`/design/principles/`](/design/principles/) — long-form articles on the five ideas above
- [`/design/changelog/`](/design/changelog/) — semver-locked release history, ending with v1.0.0 today

Every component page ships a live preview, a copy-paste code block, a do/don't list, an anatomy diagram, and an accessibility note. The component pages are themselves rendered through the design system, which is the strongest possible smoke test.

## What surprised us during the sprint

**The `--radius-md` collision was three years old.** Three different files declared it: `8px`, `12px`, `16px`. The "winner" on any given page was whichever stylesheet the route bundler loaded last. Cards on the marketing pages used `8`, cards in the freelancer portal used `16`, cards on the blog used `12`, and nobody had noticed because nobody loaded all three routes back-to-back. The codemod that consolidated these to a single `--ds-radius-md: 12px` shipped 8,814 token replacements; we visually diffed every changed page in Playwright before merging.

**Astro doesn't allow dynamic slot names.** This bit us building the Tabs primitive: we wanted `` to render into a slot named `"Overview"` on the parent `

## Where to look

- The reference site: [/design/](/design/)
- The five principles, long-form: [/design/principles/](/design/principles/)
- The release history: [/design/changelog/](/design/changelog/)
- The GitHub repo: currently private; opening publicly alongside v1.1. Until then, the reference site is the canonical source.

If you find a bug, an a11y regression, or a token that should exist but doesn't, the fastest way to reach us is `design@yourpersonalai.net`. We will open a public issue tracker when the repo flips.

We built this for ourselves. We are publishing it because the conversations we needed to have during the sprint — about cascade order, about hub identity, about reading-surface ergonomics, about what "reduced motion" should mean as a *contract* — were conversations the rest of the design-system world is also having. If any of the five ideas above lands, take them. None of them are ours.

## Footnotes

[^1]: Full audit at `docs/ds-audit-2026-05-12.md`. The audit script (`scripts/audit/token-inventory.mjs`) is now part of `npm run audit:tokens` and runs in CI.
[^2]: The `data-motion` contract is documented at [/design/motion/](/design/motion/). The TL;DR: `none` disables decorative motion, `subtle` keeps functional transitions (focus rings, dialog enter/exit) but no decorative reveals, `full` is the default. `prefers-reduced-motion: reduce` globally rewires every `--ds-motion-duration-*` token to `0s` — components do not need per-element opt-ins.
[^3]: We chose Pagefind over Algolia for three reasons: it's static (no API key in the client bundle), it costs nothing (Algolia's free tier rate-limits aggressively at our traffic), and the quality is good enough that we cannot tell the difference at 83 blog posts. The "good enough" line will move; we'll revisit at ~500 posts.
[^4]: Astro slot names must be string literals at compile time — they cannot be expressions. This is well-documented at [docs.astro.build/en/basics/astro-components/#named-slots](https://docs.astro.build/en/basics/astro-components/#named-slots) and is by design (server-side slot routing is one of the things that keeps Astro's hydration story simple). The workaround pattern we landed on — `data-tab-panel="<name>"` as a sibling attribute, with a small client script wiring panels to triggers — is documented on [/design/components/tabs/](/design/components/tabs/).
[^5]: The CSS cascade order treats unlayered styles as "more important" than any `@layer`, which is the opposite of what most developers expect on first read. See [MDN: Cascade layers](https://developer.mozilla.org/en-US/docs/Web/CSS/@layer) for the canonical explanation. We ended up using `@layer` for intra-system precedence only and ordering `@import` statements for cross-system precedence.