@snapt/ui
v0.2.0
Published
Snapt's design system: tokens, Tailwind theme and React components.
Readme
@snapt/ui
Snapt's design system: tokens (colors, type, radii, shadows) and the React
components every Snapt app is built from, published on npm as
@snapt/ui.
npm install @snapt/uiimport { Button } from "@snapt/ui/components/Button";
import { PageHeader } from "@snapt/ui/components/PageHeader";
import { AppLayout } from "@snapt/ui/components/AppLayout";
import { colors } from "@snapt/ui/tokens";It ships compiled ESM + type declarations (dist/), with react (18 or 19)
as a peer dependency and lucide-react for icons — no transpilePackages needed. Browse everything in
Storybook: npm run storybook.
What's in it
| Import | What |
| --- | --- |
| @snapt/ui/tokens | Token values in TS: colors, fonts, radii, shadows |
| @snapt/ui/theme.css | The tokens as CSS variables (--snapt-color-bg, …) + base page styles |
| @snapt/ui/tailwind.css | Tailwind v4 theme mapping utilities onto the variables |
| @snapt/ui/tailwind-preset | The same mapping as a Tailwind v3 preset |
| @snapt/ui/components/* | AppLayout, BrandMark, Logo (SnaptLogo, SnaptMark), Nav (NavGroup, NavItem, BottomNav…), PageHeader, Button, button-styles, Card, StatCard, Badge, Avatar, ProgressBar, Field (Input, Select, Textarea), SegmentedControl, Sheet, EmptyState |
The look lives in src/tokens.ts only. Components use
utility names (bg-panel, text-muted, rounded-lg, font-mono…) that
resolve to the CSS variables, so changing a token restyles every app.
Setup in an app
Tailwind v4 — in the global CSS:
@import "tailwindcss";
@import "@snapt/ui/tailwind.css"; /* theme + variables, and scans the components */Tailwind v3 — in tailwind.config.ts:
import snaptPreset from "@snapt/ui/tailwind-preset";
export default {
presets: [snaptPreset],
content: ["./src/**/*.{ts,tsx}", "./node_modules/@snapt/ui/dist/**/*.js"],
};and import @snapt/ui/theme.css once (e.g. in the root layout).
Fonts. The look follows snapt.tech: Inter, light
weights for large headings, tabular numerals for figures. Load Inter in the
app; with next/font, point the variable at the generated family:
const inter = Inter({ subsets: ["latin"], variable: "--font-sans" });:root {
--snapt-font-sans: var(--font-sans), system-ui, sans-serif;
}Links and routers
next (or any router) is deliberately not a dependency: a component built
against a different copy of next than the app's own would silently fail to
navigate. Components that render links take the app's link component instead:
import Link from "next/link";
<NavItem as={Link} href="/cantieri" icon="site" label="Cantieri" active />For a link styled as a button, wrap your own Link around buttonClassName():
import Link from "next/link";
import { buttonClassName } from "@snapt/ui/components/button-styles";
<Link href="/sign-in" className={buttonClassName("secondary", "lg")}>Accedi</Link>Icons are Lucide: import them from lucide-react in
the app and pass elements — icon={<Building2 />}. Components size them; a
<Button> sizes any <svg> inside it.
Interactive components (SegmentedControl, Sheet) carry "use client"; the rest
render on the server.
Developing and releasing
npm install
npm run build # tsc → dist/, then tokens → dist/theme.css, dist/tailwind.css
npm run typecheck
npm run storybook # http://localhost:6006Every component gets a story in stories/. CI typechecks, builds the package
and builds Storybook on every push and pull request.
To release: bump version in package.json (semver: a changed class or
prop is a breaking change for apps), merge to main, then push a matching
tag — git tag v0.2.0 && git push origin v0.2.0. The Publish workflow
typechecks, builds and publishes it through npm trusted publishing (no
token). This repo is private, so npm doesn't attach a provenance
attestation. Apps pick it up by bumping their dependency.
Try a change in an app before releasing: npm pack here, then
npm install --no-save ../path/to/snapt-ui-<version>.tgz in the app.
