@hraness/ui is a source-first set of accessible React primitives and shared styles for Tailwind CSS applications. It combines React Aria Components behavior, semantic data-attribute variants, class composition, portable theme tokens, and a restrained application baseline.
The package publishes built ESM runtime entry points and ships its TypeScript and TSX source for declarations, inspection, and contribution. Its CSS exports remain source files and provide the complete default theme as well as separate token, reset, component, and Tailwind integration layers.
Pin an immutable release from GitHub:
{
"dependencies": {
"@hraness/ui": "github:hraness/ui#v0.2.0"
}
}Then install with Bun:
bun installReact 18 or 19 and React DOM 18 or 19 are peer dependencies.
Import Tailwind once, then import the complete UI stylesheet before product-specific rules:
@import "tailwindcss";
@import "@hraness/ui/styles.css";
/* Optional product-level token overrides and styles follow. */styles.css registers the package source with Tailwind, defines the shared light and dark themes, applies the portable reset, and includes component recipes. Consumers do not need a fragile node_modules-relative @source path. The stylesheet expects Tailwind CSS v4 processing; it deliberately does not import Tailwind itself, which prevents duplicate Preflight and utility output.
Set data-theme="dark" or the dark class on a root element to select the dark recipe. Set data-theme="light" for an explicit light island. Product themes override the namespaced roles such as --ui-background, --ui-primary, and --ui-ring after the imports; Tailwind's familiar bg-background, text-muted-foreground, and related utilities remain available through the included bridge.
For a narrower integration, import any static layer directly:
@import "@hraness/ui/tokens.css";
@import "@hraness/ui/reset.css";
@import "@hraness/ui/components.css";tokens.css and reset.css are standards-only CSS and do not require Tailwind. tailwind.css owns only source detection, the dark variant, and semantic Tailwind mappings.
import {
Badge,
Button,
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
TextField,
} from "@hraness/ui";
export function ProjectCard() {
return (
<Card className="max-w-md" tone="card">
<CardHeader>
<Badge tone="success">Local</Badge>
<CardTitle>Local preview</CardTitle>
<CardDescription>A Vite application running on this computer.</CardDescription>
</CardHeader>
<CardContent>
<TextField
description="Used for development notices."
label="Email"
placeholder="you@example.com"
type="email"
/>
</CardContent>
<CardFooter>
<Button
onPress={() => console.log("Open preview")}
variant="primary"
>
Open application
</Button>
</CardFooter>
</Card>
);
}Use React Aria's onPress event for actions. Action controls use the semantic primary, secondary, quiet, and danger variants and the compact, default, and large sizes. IconButton and IconLink require an accessible name and own their hover/focus tooltip; aria-label supplies the default visible copy, while controls named by aria-labelledby must also provide tooltip.
Connect links to a client router once at the application boundary. Internal links then navigate through the router and prefetch once on hover or focus; external, fragment-only, and protocol-relative links never prefetch:
import { RouterProvider } from "@hraness/ui";
<RouterProvider
navigate={(href) => router.push(href)}
prefetch={(href) => router.prefetch(href)}
>
<App />
</RouterProvider>The public barrel includes:
- Actions:
Button,IconButton,IconLink,ToggleButton,Link, andLinkButton. - Forms:
Form, text and text-area fields, search and number fields, checkbox and radio groups, switches, native and React Aria selects, and file fields. - Collections: tabs, disclosures and accordions, toggle groups, segmented controls, list boxes, and separators.
- Overlays: menus, dialogs, popovers, tooltips, and an isolated toast provider and queue.
- Feedback and data: badges, status dots, alerts, spinners, skeletons, progress, meters, sliders, avatars, and data tables.
- Content and layout: cards, pressable and themed surfaces, page intros, empty states, settings cards, toolbars, breadcrumbs, pagination, skip links, viewport frames, and wrapping rows.
Interactive primitives preserve React Aria state through data-hovered, data-pressed, data-selected, data-invalid, data-focus-visible, and related attributes. The shared CSS includes pointer-coarse target sizing, reduced-motion fallbacks, forced-color support, and visible focus treatment.
Override semantic roles after the imports to reskin the whole system without depending on component internals:
:root {
--ui-primary: oklch(0.52 0.16 250);
--ui-ring: oklch(0.62 0.14 250);
--ui-radius: 1rem;
}Every primitive accepts className. Actions also expose controlClassName when the nested semantic control needs a focused override:
<Button className="max-w-full" controlClassName="rounded-xl" variant="quiet">
Open application
</Button>The package exports cn for consumer-side class composition. Treat documented component classes and data-slot values as stable styling hooks; prefer token overrides for system-wide changes.
Version 0.2 replaces recipe helpers with semantic, styled primitives and is intentionally breaking. Rename action variants from default, destructive, outline, ghost, and link to the closest role among primary, danger, secondary, and quiet; replace sm, lg, and icon sizes with compact, large, and IconButton/IconLink; and replace TextField isLabelHidden with showLabel={false}. buttonVariants and badgeVariants are no longer exported. Conventional ref values continue to target the semantic button and field root.
Read CONTRIBUTING.md before opening a pull request. Report suspected vulnerabilities privately as described in SECURITY.md.
MIT