A design system usually has two copies: the one designers maintain in Figma and the one engineers maintain in code. They drift apart a little with every release. Nexera UI avoids the second copy. The Figma file is the source of truth for visuals, and the code is generated from it or checked against it.
Tokens are generated, never typed
The colours, radii, sizes, shadows and text styles in @nexera-ui/tokens come from the Figma variables and styles. Nothing in src/generated is edited by hand.
The pipeline has three steps:
- Read-only plugin snippets in
packages/tokens/figma-read/read the variables and styles from the Figma file. Each read returns the data with an FNV-1a checksum. - The data is saved as a committed snapshot in
packages/tokens/figma/, and the checksum is verified against the copy. pnpm tokens:generateturns the snapshot intotokens.css, the Tailwind v4theme.cssand a typedtokens.ts.
We read through the Figma MCP because the Figma Variables REST API is only available to Enterprise organisations. The snapshot makes the result reproducible: anyone can regenerate the tokens offline, and CI runs pnpm tokens:generate and fails if the output differs from what is committed. A design change arrives as a diff of the snapshot and the generated files, reviewed together.
Names survive the trip
A token keeps its Figma name all the way to your class list. bg/surface in Figma becomes --color-bg-surface in CSS and bg-surface in Tailwind. status/success/on-solid becomes --color-status-success-on-solid. A designer and an engineer can point at the same name.
The Figma modes survive too. Light and Dark become :root and .dark (or [data-theme]) scopes. The Web, iOS and Android modes of the Radius and Size collections become [data-platform] scopes, which is how platform="ios" gives a button its 44 pt height.
Some values are not Figma variables, and the tokens README lists them as deviations: the z-index scale, the motion tokens and the focus indicator geometry, a 2 px outline with a 2 px offset taken from the Figma Elevation page.
Components keep Figma's vocabulary
Each component starts from a brief generated from the Figma snapshot. The brief lists the Figma node, every variant property and option, the default variant, the states, and a table that maps each Figma property to a prop. For Checkbox, the Figma Value property (Unchecked, Checked, Indeterminate) becomes isSelected and isIndeterminate, and the State options Disabled and Error become isDisabled and isInvalid.
The rules are strict. Component names stay Figma's names. Variant options stay Figma's options. The Figma Mobile sets become a platform prop on the web component instead of a separate ButtonMobile. Most props carry a TSDoc comment that names the Figma property, so size on OTPInput reads "Cell size (Figma Size)".
Where we depart from Figma
Figma decides how things look. It does not get the final word on behaviour or accessibility, and when the two conflict, the component follows the accessibility rule and the conflict is written down. Some examples:
- Form labels in several Figma components are auto-width and overflow. In code they wrap.
- Some Figma components have no hover or focus state. Code adds them, derived from existing state tokens with no new colours.
CarouselIndicatordots are 24 px targets for WCAG 2.5.8, and inactive dots useborder/input, because the Figmaborder/strongfalls below 3:1.
The tokens get the same treatment. Five Figma colour pairs fail WCAG AA, and they are listed in KNOWN_CONTRAST_EXCEPTIONS instead of being quietly changed. A test asserts that the failing set matches the list exactly.
Checking the result against the file
Generated tokens cover colours and sizes. Layout needs checking too.
Almost every component has Figma* stories that reproduce its Figma component set. Stories can mark elements with data-expect-height, the Figma height in CSS pixels, and a Playwright test measures them in a real browser, where a stray border or padding shows up. Ten component sets, at least one per category, were also compared by eye with Figma screenshots side by side. That review caught real defects: secondary small buttons were 34 px high instead of 32, and message bubbles were about 24 px too tall because a paragraph kept the browser's default margins. A browser test now checks every story for default margins that leak in.
Starting in Figma costs time up front. It saves the slower work of finding out, months later, which copy of the design system is the right one. Browse the components to see the Figma property names in each prop table.