React
Nothing in the library is Next.js-specific. The "use client" directives are inert outside a React Server Components bundler — they are simply string literals that every other bundler ignores.
Setup
Import the stylesheet at your entry point, before your own styles.
// src/main.tsx
import "@abbainitiative/ui/styles.css";
import "./index.css";
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "./App";
createRoot(document.getElementById("root")!).render(
<StrictMode>
<App />
</StrictMode>,
);Forms
FormField generates the ids and wires aria-describedby, aria-invalid and the label's htmlFor for you. It works with uncontrolled forms, controlled state, and any form library that gives you an error string.
import { Button, FormField, Input, Stack, Textarea } from "@abbainitiative/ui";
import { useState } from "react";
export function ContactForm() {
const [errors, setErrors] = useState<Record<string, string>>({});
return (
<form onSubmit={handleSubmit}>
<Stack gap={5}>
<FormField label="Name" error={errors.name} required>
<Input name="name" />
</FormField>
<FormField
label="Message"
description="Tell us what you need."
error={errors.message}
>
<Textarea name="message" autoSize />
</FormField>
<Button type="submit">Send</Button>
</Stack>
</form>
);
}Toasts
Mount ToastProvider once near the root, then call toast() from anywhere below it.
import { ToastProvider, useToast } from "@abbainitiative/ui";
function App() {
return (
<ToastProvider>
<Routes />
</ToastProvider>
);
}
function SaveButton() {
const { toast } = useToast();
return (
<Button
onClick={async () => {
await save();
toast({ title: "Saved", tone: "success" });
}}
>
Save
</Button>
);
}StrictMode
// StrictMode double-invokes effects in development. That is a
// feature, not a bug — nothing in ABBA UI relies on an effect running
// exactly once, so components behave identically under it.Bundler notes
- Vite, Rspack, webpack 5, Parcel, esbuild. All resolve the
exportsmap with no configuration. - Remix / React Router. Import the stylesheet through the route module's
linksexport, or as a side-effect import in the root route — both work. - Astro. Use the React integration and import the stylesheet in your layout. Components need a
client:*directive only where they are actually interactive, which mirrors the Next.js split closely. - Jest. Add a
moduleNameMapperentry mapping\.css$to a stub. Jest does not understand CSS imports, and the package imports its stylesheet from the entry file. Vitest handles this without configuration.
Tree-shaking
The package is published as ESM with per-component chunks and "sideEffects": ["**/*.css"], so a bundler can drop everything you do not import while keeping the stylesheet. If your bundler has weak side-effect analysis, import from the subpath — @abbainitiative/ui/badge — which makes the boundary explicit.
Next: the token reference.