Deep dive Design System Figma → Vue.js WCAG 2.2 AA 2 vibe-coded plugins

A 250+ component design system, built from zero, with two custom plugins keeping it honest.

The HikeOn ERP design system is the source of truth for a custom eCommerce ERP serving a US-based foodservice and packaging brand with a 16,000-SKU catalog. I lead its architecture, governance, adoption, and tooling. This is the deep-dive companion to the HikeOn case study, focused entirely on how the system is built and how it stays healthy.

Role
Design Systems Lead
+ AI Product Design Lead
Team
6-member design team
Daily sync with eng architects
Timeline
Oct 2024 to present
Built from zero
Scale
250+ components · multi-mode
Figma → Vue.js · web + mobile
01 · The problem

No shared system. Designers improvised. Engineers rebuilt the same components per module.

When I joined HikeOn, the product team was building a custom eCommerce ERP from scratch with no shared system. Designers improvised in each file. Engineers rebuilt similar components per module. Three problems compounded fast:

  • Visual and behavioural drift across modules. Buttons, form fields, and tables looked and behaved differently depending on who built them.
  • Tokens were inconsistent. Colors, spacing, and typography were hardcoded in Figma and again in code. Theming and white-labeling for multiple tenants was effectively impossible.
  • Handoff friction was high. Engineering had to interpret design intent every time. Discrepancies between Figma and shipped UI surfaced late in QA, when fixes were expensive.

On top of that, the product had a growing surface area: order management, catalog, purchasing, AI-assisted workflows, dashboards, and customer-facing storefronts. Without a system, the team would lose more time to rework than to new features.

02 · Discovery

Before fixing it, I measured how bad the drift actually was.

I didn't start with a token spec. I started with an audit. In the first weeks I catalogued every component already living across the product files, read the front-end codebase alongside it, and sat with the engineers during a real handoff to watch where intent got lost. The point was to replace "the system feels inconsistent" with something I could act on.

What the audit surfaced.

  • The same component, rebuilt multiple ways. Distinct button, input, and table treatments existed across modules, none of them the canonical one, because there was no canonical one.
  • Tokens were defined twice and agreed nowhere. Colour, spacing, and type were hardcoded in Figma and again in code, with no shared names. Theming or white-labelling a second tenant would have meant editing both, by hand, forever.
  • Handoff was a translation tax. Engineering re-interpreted design intent on every component; discrepancies surfaced late in QA, when fixes were expensive.
  • Contribution was already happening, ungoverned. Designers outside any core were adding components ad hoc. The system would either absorb that energy or fight it.
  • Drift was invisible. Nobody could point to how much inconsistency existed or whether it was growing. There was no instrument.

Each finding above became a goal. The five goals in the next section aren't a wishlist. They're the audit, turned into commitments.

PHASE 1

Audit & alignment. Component inventory, codebase read, handoff observation, goals signed off with engineering.

PHASE 2

Token foundation. Primitive → semantic → component layers; Light and Dark modes resolved in one collection.

PHASE 3

Primitives & first composites. Built atoms, then the first real components, and locked the Figma → Vue naming contract against them.

PHASE 4

Pipeline & handoff protocol. Storybook stories, annotation blocks, dev-mode diff checklist.

PHASE 5

Governance & tooling. Contribution model, quality gates, and the two internal plugins.

ONGOING

Measure. Weekly adoption + debt metrics with delta.

03 · Goals

A single source of truth that measures its own health.

Each goal maps directly to a problem the audit surfaced: same order.

  • Ship a single source of truth for design, structured so engineers can map directly to Vue.js components without translation.
  • Build a token architecture that supports theming, dark mode, density, and white-label brands without forking components.
  • Establish a contribution model that lets designers and engineers outside the core team add to the system without breaking consistency.
  • Make accessibility a default, not a late-stage fix.
  • Track adoption and design debt as measurable metrics, not vibes.
04 · Figma architecture

Four libraries, three token layers, one naming contract.

Library structure. The system is split into four publishable Figma libraries. Each has a clear scope and depends only on the layer beneath it. This keeps blast radius small when a token or primitive changes.

LibraryScope
01_TokensPrimitive and semantic variables for color, spacing, typography, radius, elevation, motion. No components.
02_PrimitivesAtomic components: icons, avatars, badges, dividers, base inputs. Consume only tokens.
03_ComponentsComposite components: buttons, form fields, data tables, navigation, modals, charts, AI interaction patterns. Consume primitives and tokens.
04_PatternsPage-level and flow-level patterns: empty states, error states, AI confidence states, copilot panels, multi-step forms.
DECISION

Four libraries, not one. The faster setup was a single library. I split into four layered libraries instead, each depending only on the layer beneath it. The trade-off is more publish steps; the payoff is a small blast radius, a token change can't silently ripple into a page-level pattern.

Four-layer library dependency diagram

Token architecture. Tokens use a three-layer model. Primitive tokens hold raw values. Semantic tokens describe intent. Component tokens map intent to specific UI roles. This is what makes theming and white-labeling possible without forking components.

LayerExample & rule
PrimitiveRaw values in the Colors collection (Colors:Grey/Grey-1200, Colors:brand/color-brand-900-brand, Colors:white/Color-white-1000, Colors:Pallet/Pallet-01-default). Designers never reference these in components.
SemanticTEXT/text-defaultGrey/Grey-1200 (Light) / white/Color-white-100 (Dark) · BACKGROUND/bg-page-primarywhite/Color-white-1000 (Light) / Grey/Grey-1200 (Dark). Describes intent. Resolves per mode.
ComponentBUTTON/bg-button-primarybrand/color-brand-900-brand · ICON/icon-activebrand/color-brand-900-brand (Light) / brand/color-brand-1500 (Dark). Maps semantic intent to a specific UI role.
DECISION

Three token layers, not flat. A flat or semantic-only token set would have shipped sooner. But a second tenant or a white-label brand would then have forced a fork per brand. The component-token layer is the deliberate cost I paid up front so that adding a brand is "a mode, not a fork." That single decision is why the multi-mode demo below works at all.

The semantic and component layers live in a single Figma collection, Global_combinations, that resolves across Light and Dark modes. Because every component token aliases a semantic or primitive variable, and semantic tokens even alias each other, with ICON/icon-success and BORDER/border-input-selected both pointing at ICON/icon-active, adding a new white-label brand is a matter of adding a mode, not forking components. The same collection is exported to engineering as token JSON consumed by the Vue.js build, so a mode switch in Figma and a mode switch in code use the same names.

Multi-mode in practice. The charts data-visualisation palette, shown with its real Light and Dark values straight from the collection, one semantic name, two resolved tints:

Royal Blue
Light
#e9eeff
Dark
#09143a
Blue
Light
#e5f9ff
Dark
#083340
Ink Blue
Light
#f1f1ff
Dark
#242459
Green
Light
#f2ffe4
Dark
#29430c
Dark Green
Light
#e2fff3
Dark
#0d3021
Yellow
Light
#ffffe4
Dark
#4b3d04
Orange
Light
#ffefe8
Dark
#3c1a0a
Red · Danger
Light
#feeae9
Dark
#3f1515
Pink
Light
#f8e8ff
Dark
#470d61
Purple
Light
#f8f2ff
Dark
#2f1e42
Grey Font
Light
#434d56
Dark
#a3afb9
Four-layer library dependency diagram

Naming conventions. Naming is the contract between design and code. Locked on day one, unchanged since:

ElementRule
TokensGROUP / role-element-variant, grouped by intent (TEXT, BACKGROUND, BORDER, BUTTON, ICON, ALERT, Status), kebab-case roles. e.g. BUTTON/bg-button-primary, TEXT/text-placeholder-muted, BORDER/border-input-selected
ComponentsPascal-case in Figma matches Pascal-case in Vue. Button / DataTable / FormField. Variant property names match on both sides.
VariantsStandard set across every component: Size · Variant · State · Density. Always in that order. No component invents a new axis without review.
SlotsComponent properties named after Vue slots: leading, trailing, content. One-to-one map to engineering.
DECISION

Lock the naming contract on day one. The alternative was to let names emerge and rename later. I rejected it: the Figma → Vue one-to-one map only holds if names are stable, and a rename downstream breaks the pipeline silently. The trade-off is real, some early rigidity, a few names I'd phrase differently today, but the contract has held unchanged, which is the whole point.

Auto Layout standards. Every component uses Auto Layout, including icons.

  • Padding, gap, and direction come from spacing tokens, never raw numbers.
  • Resizing behaviour is set explicitly per component: fill, hug, or fixed. Documented on the component page.
  • Nested Auto Layout depth is capped at 4 levels. Anything deeper triggers a refactor review.
Four-layer library dependency diagram

Branching, publishing, versioning.

  • Every change goes through a branch. Direct edits to main are blocked by convention and called out in review.
  • Publishing follows semver: patch for token tweaks, minor for new components or non-breaking variants, major for renamed props or removed components.
  • Release notes are written alongside the merge, not after. Notes name the components changed, the engineer to notify, and the migration if any.
05 · Figma → code pipeline

The Figma component is the spec. Engineering shouldn't have to interpret.

This works because the structure is identical on both sides, names, props, slot names, token references all map one-to-one.

FigmaVue.js
Component nameVue SFC name. Button.figButton.vue
Component propertyVue prop with matching name and type. Variant=primaryvariant: 'primary'
Boolean propertyVue boolean prop. Disabled=truedisabled: true
Slot propertyVue named slot. Leading<slot name="leading" />
Variable (token)CSS custom property exported by Style Dictionary. color.bg.brand--color-bg-brand
Variant matrixStorybook story per variant combination, generated from the same matrix.

Handoff protocol.

  • Every component page in Figma carries a fixed annotation block: purpose · props · states · accessibility notes · motion spec · do & don't.
  • Engineering reviews the component in Figma before build, not after. Open questions are resolved at this stage.
  • After build, we run a side-by-side diff in dev mode against a fixed checklist of states.
  • Discrepancies are logged as system-level bugs, not feature bugs. They get fixed at the component, not patched per screen.
06 · Tooling

Two vibe-coded Figma plugins, both in daily use.

I vibe-coded two internal plugins using Cursor and Claude Code to automate the parts of system maintenance that were eating the team's time. Both are in active use.

Plugin 01

Similar Component Finder

Problem: As the system grew, designers in different modules quietly recreated near-duplicates. Audit was a manual, multi-day job.

What it does: Scans the file, clusters near-duplicate components using structural and semantic similarity, and surfaces consolidation candidates with side-by-side diff previews.

Days → minutesAudit time
Pre-releaseUsed before every release
Plugin 02

Design Debt Collector

Problem: Detached instances, hardcoded colors that bypassed tokens, deprecated components still in use, missing variants. Design debt was real but invisible.

What it does: Audits files for all four categories and exports a triage report with file, frame, and component references. Tracks delta week over week.

Ranked backlogNot a vague worry
Weekly deltaShared in design review

AI in the daily workflow. Beyond the plugins, AI is part of how the team works. Cursor + Claude Code for plugin development. Google Stitch + Gemini Code Assist for prototype generation. Claude for documentation drafts and decision-record summaries. The principle: automate the repetitive, free the team for the judgment calls.

07 · Governance & contribution

A contribution model that scales beyond the core team.

RoleResponsibility
System owner (me)Roadmap, architecture, breaking changes, final review.
Core contributorsTwo designers and one front-end engineer. Triage intake, build and review components, maintain documentation.
Product contributorsAny designer or engineer outside the core. Can propose and build, with required review steps.

Contribution flow.

  1. Intake: contributor opens a request with problem, proposed component, and screens where it is needed.
  2. Triage: core team decides build / defer / use existing. Decision posted in the system channel.
  3. Build: contributor builds on a Figma branch following naming, variant, and Auto Layout standards.
  4. Review: core team reviews against the quality gates. Engineering Architect signs off on the implementation plan.
  5. Merge & document: component merged, release notes published, documentation updated, engineer notified.

Where the "no new axis without review" rule came from. Early on, a product contributor built a new component with a custom variant axis that read perfectly in isolation. In review it broke two things at once: the shared mental model across components, and the one-to-one prop map to Vue. Working with the Engineering Architect, we collapsed it back into the standard Size · Variant · State · Density set in the same session. That incident is why the rule exists, it wasn't handed down, it was learned. The Engineering Architect now signs off on every implementation plan at the Review step for exactly this reason.

Quality gates. No component ships without passing all four:

  • A
    Accessibility
    WCAG 2.2 AA contrast, keyboard support, focus management, screen reader semantics documented.
  • C
    Consistency
    Uses tokens only. No hardcoded values. Follows naming and variant standards.
  • D
    Documentation
    Purpose, props, states, do & don't, motion spec, accessibility notes filled in.
  • I
    Implementation parity
    Vue component built and reviewed against the Figma component before close-out.
08 · Accessibility

Baked into tokens, components, and patterns. Not a late-stage fix.

  • Token level. All color pairings used in semantic tokens are pre-checked against WCAG 2.2 AA. APCA is used for borderline calls. A failing color pair simply does not exist in the semantic set.
  • Component level. Each component carries an accessibility spec: keyboard interactions, focus order, ARIA semantics, screen reader announcements, error association. Engineering uses this directly in build.
  • Pattern level. Flow-level patterns (forms, modals, AI confirmations, multi-step wizards) carry their own accessibility notes covering focus traps, live regions, and error recovery.
Four-layer library dependency diagram
09 · Adoption & validation

The system has to prove it's working, every week.

A design system claims its value; it doesn't get to assert it. Two numbers run on a weekly cadence and get read out in design review, with the delta from last week.

  • Adoption. The percentage of screens in active product files using system components versus detached or hand-rolled instances. The number that tells us the system is being used, not bypassed.
  • Debt. The Design Debt Collector flags detached instances, hardcoded colour values, deprecated components still in use, and missing variants. The delta week over week is the signal, flat or rising means the contribution model is leaking somewhere.

One loop, start to finish. When the debt report flags a cluster of detached instances in a module, we trace them to their source: usually a frame that predates the canonical component, or a contributor who wasn't aware it existed. We rebuild that module on the system component and log it as a system bug. Not a per-screen patch, so the same class of issue doesn't recur. The next week's report shows the delta. That loop is what turns a dashboard into a practice.

10 · What I'd do differently

Three things I'd do earlier next time.

EARLIER

Lock the token JSON export contract with engineering on day one. We retrofitted it later and paid a small migration cost.

EARLIER

Start the Design Debt Collector plugin earlier. By the time it shipped, we already had a backlog of debt to triage.

EARLIER

Invest in dev-mode-ready annotations from the first component, not from the tenth. Going back to annotate is slower than annotating as you build.

11 · Outcomes

A living product, not a deliverable.

250+
COMPONENTS · WEB +
MOBILE · MAPPED TO VUE.JS
Multi-mode
THEME · DARK · DENSITY ·
WHITE-LABEL · NO FORKS
2
INTERNAL PLUGINS
IN DAILY USE
Weekly
ADOPTION + DEBT
METRICS WITH DELTA
8+
AI USE CASES SHIPPED
ON SYSTEM PRIMITIVES
WCAG 2.2 AA
BAKED INTO TOKENS,
COMPONENTS, PATTERNS
6-member
DESIGN TEAM
+ EXTERNAL CONTRIBUTORS
From zero
OCT 2024 →
NOW & ONGOING

The system is a living product, not a deliverable. Treating it that way, with a roadmap, contributors, release notes, quality gates, and metrics, is what keeps it healthy as the surface area grows. The work continues. (Closing principle)