@boopalakannan-ux/test-ds
v0.4.0
Published
A small SaaS component system — Button, Input, Badge and Table, driven by semantic design tokens mirrored 1:1 from Figma.
Downloads
516
Maintainers
Readme
@boopalakannan-ux/test-ds
A small React component system — Button, Input, Badge, Table — driven by semantic design tokens mirrored 1:1 from a Figma library. Built for use as a Figma Make kit.
npm install @boopalakannan-ux/test-dsimport "@boopalakannan-ux/test-ds/styles.css";
import { Button, Input, Badge, Table } from "@boopalakannan-ux/test-ds";The stylesheet is a separate export. Import it once at your app root or every component renders unstyled.
What's inside
src/
Button.tsx / .css
Input.tsx / .css
Badge.tsx / .css
Table.tsx / .css
tokens.css the code mirror of the Figma Semantic, Spacing and Radius collections
index.ts
guidelines.md how Make should use the system ← the highest-leverage fileComponents
Button
<Button variant="primary" size="md">Save changes</Button>
<Button variant="secondary" leadingIcon={<Download />}>Export PDF</Button>
<Button variant="destructive" loading>Deleting</Button>
<Button variant="ghost" leadingIcon={<Settings />} aria-label="Settings" />| Prop | Type | Default |
| --- | --- | --- |
| variant | primary \| secondary \| ghost \| destructive | primary |
| size | sm \| md \| lg | md |
| loading | boolean | false |
| leadingIcon / trailingIcon | ReactNode | — |
Input
<Input
label="Email"
status={error ? "error" : "default"}
helpText={error ?? "We'll only use this for sign-in."}
/>| Prop | Type | Default |
| --- | --- | --- |
| size | sm \| md \| lg | md |
| status | default \| error \| success | default |
| label | string — plain text only, see below | — |
| helpText | ReactNode | — |
| leadingAdornment / trailingAdornment | ReactNode | — |
| required, fullWidth | boolean | false |
label is a string, deliberately. Its contents land inside <label htmlFor> and
become the field's accessible name — a Badge in there makes a screen reader announce
"Workspace name Pro" as the name of the input.
Badge
<Badge color="green" dot>Available</Badge>
<Badge variant="filled" color="red">Failed</Badge>| Prop | Type | Default |
| --- | --- | --- |
| variant | soft \| filled \| outlined \| ghost | soft |
| color | gray \| blue \| green \| yellow \| red \| purple \| indigo \| teal | gray |
| size | sm \| md \| lg | md |
| dot | boolean — live status only | false |
| count | number | — |
| onDismiss | () => void | — |
Table
<Table
caption="Charge points across all sites"
rows={sorted}
rowKey={(cp) => cp.id}
sort={sort}
onSortChange={setSort}
activeKey={openId}
columns={[
{ key: "site", header: "Site name", sortable: true,
cell: (cp) => cp.name, subCell: (cp) => cp.site },
{ key: "power", header: "Power", align: "end", sortable: true,
cell: (cp) => `${cp.kw} kW` },
{ key: "status", header: "Status",
cell: (cp) => <Badge color={statusColor(cp.status)} dot>{cp.status}</Badge> },
]}
/>Table renders the sort control and sets aria-sort, but does not sort your data —
you hold sort in state and sort the rows. That keeps client-side and server-side
sorting the same shape.
selectedKeys + onSelectionChange enable the checkbox column with indeterminate
select-all. activeKey marks the row open in a detail panel and outranks selection.
Token parity with Figma
src/tokens.css mirrors three Figma collections name-for-name and value-for-value:
| Collection | Count | Prefix |
| --- | --- | --- |
| Semantic | 70 | --color-* |
| Spacing | 10 | --spacing-* |
| Radius | 6 | --radius-* |
Each Figma variable carries its CSS name as WEB code syntax, so Figma's inspect panel
shows var(--color-action-primary) directly.
Every Button, Input and Badge variant in Figma is bound to these variables — zero hardcoded fills across 126 variants. There is no build step syncing Figma and code; when you add a variable in one, add it in the other in the same pass.
Two rules worth knowing:
--color-status-errorand--color-action-destructiveare both red/600. An errored field and a delete button are the same idea, so they're the same red.- Never use a
--size-*token as spacing.--size-md-heightis a control height that happens to equal 40px;--spacing-4xlis the gap. They diverge the day someone changes control height.
To retheme, override the custom properties on :root.
How this maps back to Figma
| Figma property | Code |
| --- | --- |
| Hierarchy (Primary/Secondary/Ghost/Destructive) | variant |
| Size (Large/Medium/Small) | size (lg/md/sm) |
| State=Disabled | disabled |
| State=Hover / State=Pressed / State=Focused | CSS pseudo-classes — not props |
| Status=Error / Status=Success | status — is a prop |
| Content=Loading | loading |
| Content=Icon Only | omit children, pass aria-label |
State is pointer position, so CSS derives it and Figma needs a variant to show it. Status is application state, so CSS can't know it and it comes in as a prop. That asymmetry is intentional — don't "fix" it in either direction.
Publishing
npm install
npm run build # verify it compiles
npm login # first time only
npm publish --access publicVersions are immutable on both public npm and Figma's private registry — you cannot overwrite, and unpublishing is only possible within 72 hours. Bump the version rather than trying to fix in place.
For Figma's private registry, see .npmrc.example.
Using it in a Make kit
- New Make file → Settings → Create a kit → Assemble your kit
- npm packages → search
@boopalakannan-ux/test-ds(public, no scope setup needed) - Paste
guidelines.mdintoguidelines/Guidelines.md - Optionally add Library styles to import the Figma variables
- Test with a prompt, then Publish kit
Make kits flatten Figma variables into raw CSS values rather than preserving names, so
guidelines.md is where the token rules actually live. It is the highest-leverage file
in this repo — more than any component.
