BIGVU

⬚ image · нужно снять

Storybook — the live component library

Скрин запущенного Storybook (npm run storybook в Angular-репо): слева список компонентов, справа один компонент с его вариантами/контролами. 1 кадр достаточно; если хочешь — второй с токенами/цветами. Лучшее визуальное доказательство, что система работает в коде.

/bigvu/storybook.png
PNG · @2x · ~1440px по ширине

BIGVU runs on one design system across four platforms — Figma, web (Angular), iOS (SwiftUI), and Android. I owned it as the single source of truth: the token architecture, the component APIs, and the code that keeps the platforms in sync.

This was a code job, not just a Figma library. I worked in the platform repos — defining tokens, shaping component APIs, and writing component code so design and product don't drift apart.

One canonical name → four platform dialects
bg-action-strong
Figma
Backgrounds (Surfaces)/Primary Action/bg-action-strong
Web
var(--bg-action-strong)
iOS
BigvuColorsSemantic.bgActionStrong
Androidmigrating
@color/bg_action_strong
Reach
4
platforms, one system
~127
colour primitives
Architecture
2-layer
colour model (primitives → semantic)
1 name
per token, every platform

Before: four platforms drifting apart

The system existed to fix problems that compound on every multi-platform product:

  • Design and code had drifted — the source of truth was unclear.
  • Each platform team named the same token differently.
  • New components were rebuilt by hand, a little different every time.
  • Light/dark and cross-platform parity were hard to verify.

A token system, not just a page of styles

The core idea: match the structure to each kind of token. Each token type gets the number of layers it actually needs — no rigid three-layer model forced onto everything.

CategoryLayersWhy
Colour2primitives → semantic — the only category that shifts with light / dark
Spacing1one flat pixel-named scale
Radius1small role-named set
Typography1role-named scale
Alpha0composed inline from RGB fragments — not a token

None of this lives only in people's heads. DESIGN.md is the single contract all four platforms follow — it sets out the token schema, how a token maps onto each platform, and the bad habits that keep the set from growing out of control.

And it's more than a document — the rules run as commands:

  • /new-token — adds a token in the right category and checks the name against the rules.
  • /audit-tokens — flags tokens used zero or one time, so they can be removed.
  • /check-drift — diffs the Figma exports against web, iOS, and Android; fails CI when they disagree.
  • /apply-tokens — applies semantic tokens to an unstyled Figma frame via the Figma MCP.
DESIGN.mdYAML · contract
# DESIGN.md — the contract between Figma, Web, iOS, Android
architecture:                  # how many layers each category actually needs
  colors:
    layers: 2
    structure: primitives → semantic   # the only category that shifts light/dark
  spacing:    { layers: 1 }    # one flat pixel-named scale
  radius:     { layers: 1 }    # small role-named set
  typography: { layers: 1 }    # one role-named scale
  alpha:      { layers: 0 }    # composed inline from RGB fragments — not a token

platform_mapping:              # one canonical name → a dialect per platform
  example_token: bg-action-strong
  figma:   Backgrounds (Surfaces)/Primary Action/bg-action-strong
  web:     var(--bg-action-strong)
  ios:     BigvuColorsSemantic.bgActionStrong
  android: "@color/bg_action_strong"

rituals:                       # the rules, runnable as commands
  - /new-token     # add a token to the right category, validate the name
  - /audit-tokens  # find tokens used 0–1 times → inline or remove
  - /check-drift   # diff Figma vs Web/iOS/Android; fails CI on mismatch
  - /apply-tokens  # Figma MCP applies semantic tokens to a frame

Colours get a semantic layer because the same role maps to different values in light and dark. Spacing, radius, and type don't — they stay flat. Alpha isn't a token at all. Every semantic token names a role, never a value — bg-action-strong, not blue-500— and components reference that layer directly, so there's no third per-component layer to maintain.

_colors-semantic.scssSCSS · Web
// _colors-semantic.scss — semantic names, never raw values
$semantic-light: (
  'bg-action-strong':        color('blue-500'),
  'bg-action-strong-hover':  color('blue-300'),
  'bg-action-strong-active': color('blue-400'),
  'bg-action-subtle':        color('blue-50'),
  'bg-action-critical':      color('red-500'),
  'border-default':          color('gray-200'),
  // …
);

One source, four platforms

The Figma variables are the source of truth, exported as .tokens.json. A generator reads that export and writes each platform's native file — CSS custom properties on the web, a typed enum on iOS, XML resources on Android. Nobody hand-copies values.

Spacing/Default.tokens.jsonJSON · Figma export
// Spacing/Default.tokens.json — exported from Figma variables
{
  "space-0": { "$type": "number", "$value": 0 },
  "space-1": { "$type": "number", "$value": 4 },
  "space-2": { "$type": "number", "$value": 8 },
  // …
}
scripts/generate-tokens.jsJavaScript · generator
// scripts/generate-tokens.js — Figma export → native token files
const TOKENS_PATH    = path.join(__dirname, '../src/lib/tokens/Light.tokens.json');
const COMPONENTS_DIR = path.join(__dirname, '../src/lib/components');

function generateTokens() {
  const tokensJson = JSON.parse(fs.readFileSync(TOKENS_PATH, 'utf8'));

  for (const componentName of Object.keys(tokensJson)) {
    if (componentName.startsWith('$')) continue; // skip metadata

    const dir = COMPONENT_MAPPING[componentName] || toKebabCase(componentName);
    // …collect this component's colour tokens from the export…

    const outputPath = path.join(COMPONENTS_DIR, dir, dir + '-colors.scss');
    fs.writeFileSync(outputPath, fileContent);
  }
}
ColorsSemantic.swiftSwift · iOS result
// ColorsSemantic.swift — the iOS result, light/dark built in
public enum BigvuColorsSemantic {

    public static var bgActionStrong: Color {
        Color.adaptive(
            light: BigvuColors.blue500,
            dark:  BigvuColors.blue600
        )
    }

    public static var bgActionStrongHover: Color {
        BigvuColors.blue300
    }
}
⬚ image · нужно снять

Terminal — generator & drift check

Скрин терминала: прогон генератора (строки «Generated …/button-colors.scss») и/или вывод check-drift. Снимается за минуту — конкретный пруф пайплайна и CI-дисциплины.

/bigvu/terminal-pipeline.png
PNG · @2x

Anatomy of one component

Every component is built to the same recipe. Watch the button come together — one token category at a time, with the reason for each.

1 · Size1 / 5

Start with dimensions — pulled from the size scale, not magic numbers. The md button is 40px tall, 12px padding, 10px radius. Every control on the same scale lines up.

sizes.ts
// sizes.ts — dimensions from the shared scale
buttonSizes.md = {
  height: '40px',
  paddingX: '12px',
  radius: '10px',
}
2 · Type2 / 5

Add type from the shared role scale: Inter, 16px, weight 600. The label now reads like the rest of the product.

typography.ts
// typography.ts — type from the role-named scale
buttonTypography.md = {
  fontFamily: 'Inter, sans-serif',
  fontWeight: 600,
  fontSize: '16px',
  lineHeight: 1.4,
}
3 · Colour3 / 5

Colour by role, not value. background → bg-action-strong, text → fg-on-color. Naming the role (not “blue-500”) is what lets one token resolve correctly in light and dark.

button-colors.scss
// button-colors.scss — colour by role
'primary-bg': 'bg-action-strong',  // → blue-500
'primary-fg': 'fg-on-color',       // → white
4 · States4 / 5

States are part of the contract — and token-driven too. Hover and active step along the blue ramp; disabled swaps to the neutral surface. Hover the button.

button-colors.scss
// button-colors.scss — states, also tokens
'primary-bg-hover':  'bg-action-strong-hover',   // blue-300
'primary-bg-active': 'bg-action-strong-active',  // blue-400
'bg-disabled':       'bg-disabled',              // gray-100
5 · Variants5 / 5

Finally, variants. One component, four roles — each maps to its own semantic tokens. Same structure and states; only the colour roles change. Switch styles above.

Style
button.ts
// button.ts — one component, four roles
export type ButtonStyle =
  'primary' | 'secondary' | 'tertiary' | 'ghost';

Live previews re-created in React from the real BIGVU tokens. The production component is the Angular one below.

That's the whole component: a typed API, every dimension and colour from a token, states and variants baked in. Assembled in code, it's just this:

button.tsTypeScript · Angular
// button.ts — typed API, styling driven by tokens
export type ButtonSize  = 'xl' | 'lg' | 'md' | 'sm' | 'xs';
export type ButtonStyle = 'primary' | 'secondary' | 'tertiary' | 'ghost';

export class Button {
  @Input() size: ButtonSize = 'md';
  @Input() variant: ButtonStyle = 'primary';
  @Input() disabled = false;
  @Input() loading = false;

  @HostBinding('style')
  get hostStyles(): Record<string, string> {
    const sizeToken = buttonSizes[this.size];
    return {
      '--btn-height':    sizeToken.height,
      '--btn-padding-x': sizeToken.paddingX,
      '--btn-radius':    sizeToken.radius,
      '--btn-icon-size': sizeToken.iconSize,
    };
  }
}

And every one of the 40 components follows that same recipe. Same structure, four platforms.

src/lib/Structure · Angular
bigvu-ui-angular/
  src/lib/
    tokens/
      Light.tokens.json         // Figma export — source of truth
      _colors.scss              // primitives
      _colors-semantic.scss     // semantic (light + dark)
      sizes.ts  typography.ts
    components/                 // 40 components
      button/  input/  modal/  dropdown/  badge/  …
  scripts/
    generate-tokens.js          // export → native token files
⬚ image · нужно снять

One component, every platform

Один компонент (напр. Button) рядом на платформах: web (Storybook) + iOS (Example в симуляторе) + Android (эмулятор) — выглядят одинаково. Главный пруф «одна система, четыре платформы». Если симулятор/эмулятор недоступны — хотя бы web + iOS.

/bigvu/component-cross-platform.png
PNG · @2x · широкий

Built to be extended — by people and agents

Forty components across four platforms only stay consistent if the rules are enforced, not just remembered. So I gave each repo rules the tools can read and check automatically — and an AI agent can extend the system without breaking it.

  • Web — a /new-component Claude Code command (and a CLAUDE.md): scaffold a component from a Figma URL, pulling the design via the Figma MCP and matching the existing token conventions.
  • iOS — Cursor rules (SwiftUI_Rules.md) pin MVVM and Clean Architecture so generated views fit the codebase.
  • Android — a firebender.json binds the architecture rules to every .kt file the agent touches.

The payoff: a new component scaffolds in minutes, already on-spec — typed API, tokens wired, states in place — instead of a hand-built one-off that drifts.

.claude/commands/new-component.mdMarkdown · agent command
Create a new Angular standalone component from this description:
$ARGUMENTS

## Step 1 — Understand the design
If a Figma URL is given, use the Figma MCP to fetch the design context
and screenshot. The Figma design is the source of truth.

## Step 2 — Study existing patterns
Before writing code, read 2–3 similar components in src/lib/components/
(button, input, badge…) and match the conventions you observe.

## Step 3 — Create the files
name.ts · name.html · name.scss · name.stories.ts
+ name-tokens/name-colors.scss — reference semantic tokens
  (bg-page-default, fg-primary), never raw hex.
firebender.jsonJSON · Android agent
// firebender.json — AI agent rules for the Android repo
{
  "rules": [
    "Write clear, concise code comments",
    {
      "filePathMatches": "**/*.kt",
      "rulesPaths": "firebender_docs/firebenderArchitectureRules.md"
    }
  ]
}
◍ video · нужно снять

Scaffolding a component with the agent

Запись экрана (15–30 сек): запускаешь /new-component с Figma-ссылкой → агент читает дизайн через Figma MCP, изучает соседние компоненты и генерит файлы по конвенциям. Видно, как система расширяется «на спеке». Если видео тяжело — скрин редактора с командой и созданными файлами.

/bigvu/agent-scaffold.mp4
MP4 · muted · loop (или PNG-скрин)

Honest about the edges

Not everything was finished — and the case shouldn't pretend it was. Android still runs a flat legacy colour list with no semantic layer; the plan, documented in the project DESIGN.md, is to regenerate light (values/) and dark (values-night/) from the same Figma exports the other platforms already consume.

And tokens have to earn their place:

  • A token exists only if it is used in more than one place.
  • No synonyms, no -2 suffixes, no per-component aliases that just forward a semantic token.
  • No token ships without a consumer — unused tokens rot.

The code

The system spans four repos — the Figma variable exports plus the Angular, iOS, and Android libraries. The Angular library ships a Storybook — the fastest way to see it running as real, interactive components.

◍ video · нужно снять

Storybook walkthrough

Короткая запись (8–20 сек, луп): скролл списка компонентов, открыть один, потыкать варианты/контролы (knobs), переключить light/dark. Живая интерактивная система — самый сильный визуал кейса.

/bigvu/storybook-walkthrough.mp4
MP4 · muted · loop
⬚ image · optional

Light / dark parity (по желанию)

Один экран Storybook в светлой и тёмной теме рядом — пруф семантических токенов. Не обязательно: тёмная тема уже видна в блоках кода.

/bigvu/storybook-light-dark.png
PNG · @2x · широкий

What it adds up to

One design language, 40 components, four platforms kept in sync from a single source — the system I owned end to end. Beyond the architecture, it:

  • Less guesswork at handoff — designers and engineers point at the same token.
  • Gave every platform one shared set of names instead of four.
  • Made token drift visible — and blocked — in CI.
  • Made building a new component repeatable instead of a one-off.