A multi-theme React component library for building plugins and tools targeting Figma, Penpot, Sketch, and Framer. Published as @unoff/ui.
src/
components/ # Component source files (7 categories)
stories/ # Storybook stories and MDX docs
styles/ # Global SCSS: layouts, texts, colors, icons, token modules
types/ # Shared TypeScript types (icon.types.ts, list.types.ts…)
index.ts # Single public export entry point
tokens/platforms/ # DTCG-format JSON design tokens per platform theme
terrazzo/ # Terrazzo build configs that compile tokens → SCSS vars
scripts/ # build-scss.js, create-theme.js
docs/ # Specs — treat each .md as the authoritative spec for its topic (theme generator, Terrazzo pipeline, …)
.claude/skills/ # Claude Code skills → see .claude/skills/
dist/ # Build output (never edit)
| Folder | Examples |
|---|---|
actions |
Button, Accordion, Card, Knob, Menu, Segmented Control |
assets |
Icon, Avatar, Text, SectionTitle, Thumbnail |
dialogs |
Dialog, Message, Notification, Consent, SemanticMessage |
inputs |
Input, Select, Dropdown, SimpleSlider, MultipleSlider |
lists |
ActionsItem, ColorItem, DraggableItem, Tabs, MembersList |
slots |
Bar, FormItem, Section, Layout, Drawer, PopIn |
tags |
Chip, Tooltip, ColorChip, IconChip |
Each component lives in src/components/{category}/{kebab-name}/ and contains at minimum:
{ComponentName}.tsx{component-name}.scss
Some components also have a styles/ subfolder and/or a {ComponentName}.figma.tsx Code Connect file.
All components use React.Component<Props, State>. Do not introduce function components or hooks in the main component source — use the existing class pattern.
export default class MyComp extends React.Component<MyCompProps> {docs
static defaultProps: Partial<MyCompProps> = { … }
render() { … }
}| Alias | Resolves to |
|---|---|
@components/* |
src/components/* |
@styles/* |
src/styles/* |
@tps/* |
src/types/* |
@stories/* |
src/stories/* |
Never use relative cross-directory imports (../../). Use aliases.
All visual values come from CSS custom properties generated by Terrazzo from the JSON token files. Naming convention:
--{component-kebab}-{variant}-{property}-{state}
Example: --button-primary-background-color-hover
The SCSS file for each component:
- Imports all four platform themes at the top:
@import 'styles/penpot'; @import 'styles/sketch'; @import 'styles/figma'; @import 'styles/framer';
- Uses only CSS variables for every themeable value — no hardcoded colors, spacing, radii, or font values.
- Overrides base variables per variant and per interaction state using the pattern in
src/components/actions/button/button.scss.
Themes are activated via HTML attributes on the root element:
document.documentElement.setAttribute('data-theme', 'figma')
document.documentElement.setAttribute('data-mode', 'figma-dark')Available themes: figma · penpot · sketch · framer
Available modes: {theme}-light · {theme}-dark (plus figjam for figma)
All public exports go through src/index.ts. Components are grouped by category. Props interfaces are exported as named types at the bottom of the file. Keep entries in alphabetical order within each group.
tokens/platforms/{theme}/*.json (DTCG format)
↓ npm run scss:build -- --build theme={theme}
↓ Terrazzo (terrazzo/{theme}/*.js configs)
src/styles/tokens/{theme}-colors.scss
src/styles/tokens/{theme}-types.scss
src/styles/tokens/modules/{theme}-colors.module.scss (re-exported via index.ts)
src/styles/tokens/modules/{theme}-types.module.scss
Token JSON files live in tokens/platforms/{theme}/ — for full pipeline documentation see docs/terrazzo-guide.md:
color.json— color palette (DTCG$type: "color")text.json— typography tokensicon.json— icon file pathstypography.json— composite typography tokenscomponents/— per-component tokens
| Script | What it does |
|---|---|
npm run storybook |
Start Storybook dev server on port 6006 |
npm run build |
TypeScript check + Vite library build → dist/ |
npm run test:storybook |
Run Vitest interaction tests (Storybook play functions) |
npm run lint |
ESLint (TS/TSX) |
npm run lint:css |
Stylelint (CSS/SCSS) |
npm run format |
Prettier check |
npm run format:fix |
Prettier auto-fix |
npm run create:theme |
Interactive new-theme generator — see docs/theme-generator.md |
npm run scss:build -- --build theme={name} |
Compile token JSON → SCSS for a specific theme |
npm run figma:publish |
Publish Code Connect to Figma |
Stories live in src/stories/{category}/. Each story file exports one named story per meaningful component variant. Every story should include a play function with at least:
- Presence assertion (
toBeInTheDocument) - Primary interaction test (click, hover, or keyboard)
args.actioncall count assertion where applicable
MDX documentation files ({CategoryTitle}.mdx) use <DocTabs> / <Tab> from src/stories/_docs/DocTabs.tsx to separate Usage and Accessibility (WCAG 2.2) guidance.
Three Claude Code skills are available in .claude/skills/:
| Skill | File | Invoke with | Purpose |
|---|---|---|---|
create-component |
.claude/skills/create-component.md | /create-component |
Scaffold a new component end-to-end |
create-theme |
.claude/skills/create-theme.md | /create-theme |
Create and configure a new brand theme |
figma-doc |
.claude/skills/figma-doc.md | /figma-doc |
Generate a Figma component description |
- Do not hardcode any color, spacing, radius, or font value in SCSS — use CSS variables only.
- Do not add imports via relative cross-directory paths — use
@components/,@styles/,@tps/. - Do not create function components or hooks in component source files.
- Do not edit files in
dist/. - Do not commit changes to
.storybook/preview.tsxtheme arrays manually — thecreate:themescript handles it. - Do not use
npm run scss:buildwithout-- --build theme=…— without the flag it only lists files.