Getting started

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 exports map with no configuration.
  • Remix / React Router. Import the stylesheet through the route module's links export, 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 moduleNameMapper entry 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.