@piloten/uids
v0.5.0
Published
Piloidis UI Design System — React Aria Components + CSS Modules over a single token layer.
Maintainers
Readme
@piloten/uids
Piloidis UI Design System — 32 React components built on React Aria Components for behaviour and accessibility, styled with CSS Modules over a single layer of design tokens.
📖 uids.piloidis.com — component browser, colour palette, type scale.
Install
pnpm add @piloten/uids react-aria-componentsreact, react-dom (18 or 19) and react-aria-components are peer dependencies. React Aria is a peer rather than a bundled dependency on purpose: two copies in one app means two copies of every React context it uses — I18nProvider, overlay state, the toast queue — and the failures are silent.
DatePicker and DateRangePicker additionally need @internationalized/date to construct values. It is an optional peer, so install it only if you use them:
pnpm add @internationalized/dateUse
Import the token layer once, at your app entry:
// app/layout.tsx, main.tsx — wherever your app boots
import '@piloten/uids/global.css' // tokens + reset + document defaults
import '@piloten/uids/styles.css' // the component stylesThen use components anywhere:
import { Button, TextField, Select, SelectItem } from '@piloten/uids'
export default function Page() {
return (
<form>
<TextField label="Title" isRequired />
<Select label="Category" defaultSelectedKey="design">
<SelectItem id="design">Design</SelectItem>
<SelectItem id="engineering">Engineering</SelectItem>
</Select>
<Button variant="primary" onPress={() => {}}>
Publish
</Button>
</form>
)
}Every component ships full TypeScript types, so prop names, variants and literal values autocomplete in your editor.
Stylesheet entrypoints
| Import | What it is |
|---|---|
| @piloten/uids/styles.css | Compiled component styles. Required. |
| @piloten/uids/global.css | Tokens + reset + body defaults. The usual choice. |
| @piloten/uids/tokens.css | Just the custom properties, if you have your own reset. |
| @piloten/uids/reset.css | Just the reset. |
Theming
Components never reference a raw colour — only semantic aliases. Redefine those and the whole system shifts:
/* your app, loaded after the package styles */
:root {
--accent: #6d28d9;
--accent-hover: #5b21b6;
--accent-text: #5b21b6;
--radius-md: 10px;
}The full token inventory — colours with live contrast ratios, the type scale, spacing, radius and elevation — is documented under Foundations on uids.piloidis.com.
Components
Avatar · Badge · Breadcrumbs · Button · Checkbox · ComboBox · Dialog · Disclosure · EmptyState · Link · ListBox · Menu · Popover · SearchField · Select · Separator · Spinner · Surface · Switch · Tabs · TagGroup · TextArea · TextField · Toast · ToggleButton · Tooltip
Plus cn (a clsx wrapper) and five icons.
Interaction state comes from React Aria as data-* attributes (data-hovered, data-pressed, data-selected, data-focus-visible, data-disabled) and is styled in CSS — there are no conditional class strings to manage.
Using it with AI tools
The package ships an llms.txt — a short brief covering setup, the token rules, and what each component is for. Point an agent at whichever is easier to reach:
node_modules/@piloten/uids/llms.txt— already there once installed- https://uids.piloidis.com/llms.txt
It's generated from the same story descriptions the docs site renders, so it tracks the release rather than drifting from it.
Notes
This package is ESM-only. Modern bundlers and Next.js handle it natively; a CommonJS require() needs a dynamic import().
Development
pnpm install
pnpm storybook # docs site on :6006
pnpm build # dist/ — ESM bundle, index.d.ts, stylesheets, llms.txt
pnpm llms # regenerate llms.txt on its own
pnpm typecheck
pnpm lintContributor conventions live in AGENTS.md.
How the build works
vite.config.ts runs Vite in library mode:
- CSS Modules are compiled here into one
dist/styles.csswith namespaced class names (uids-button-2ZuB7), so no consumer bundler needs to process.module.cssfromnode_modules. - Types are bundled into a single
dist/index.d.ts(bundleTypes). Per-file declarations would import each other with extensionless relative paths, which fails to resolve undermoduleResolution: node16and silently degrades consumer types toany. tokens.css,reset.cssandglobal.cssbypass the bundler entirely (scripts/copy-styles.mjs) so they stay readable and overridable.react,react-dom,react-aria-componentsandclsxare external — bundling a second copy of React Aria would break its context.
CI runs publint and @arethetypeswrong/cli against a packed tarball, because a published version number can never be reused.
Releasing
pnpm version minor # or patch / major
git push --follow-tagsThe tag triggers .github/workflows/release.yml, which re-runs CI, verifies the tag matches package.json, and publishes to npm with provenance. Requires an NPM_TOKEN repository secret (an npm Automation token).
The very first publish has to happen locally, since the package does not exist on the registry yet:
npm login
pnpm build
npm publish --access publicDeploying the docs
Pushing to main builds a Docker image (Storybook served by Caddy), pushes it to GHCR, and deploys it to the VPS over SSH.
Required repository secrets: SERVER_HOST, SERVER_USER, SERVER_SSH_KEY, GHCR_USER, GHCR_TOKEN.
One-time setup on the VPS:
- Point DNS: an
Arecord foruids.piloidis.comat the server's IP. - Add the block in
docker/Caddyfile.vps-snippetto the Caddyfile already running there, then reload Caddy. - Ensure
/opt/uidsexists and the deploy user can write to it.
The container serves plain HTTP on port 80 and publishes no host port — TLS and the public hostname are the existing Caddy's job. Caddy reaches it by service name over the external proxy-net network, the same one it uses to front the other sites on the box.
Licence
MIT
