Web · React 19 · Next 15 · Tailwind v4 · Radix

@avala/design

Carbon-shaped React kit, Tailwind v4 token bridge, Radix a11y backbone. Drop into any Next 14+/16+ App-Router project in five minutes.

Install

bash
npm i @avala/design @avala/design-tokens
# or
bun add @avala/design @avala/design-tokens

CSS imports

Tailwind v4 first (consumer must own this), then the kit's style bridge, then your chosen brand × tone token file.

src/app/globals.csscss
/* Tailwind v4 — owned by the consumer so postcss-import resolves
   from /node_modules. design/web/node_modules is empty on Vercel. */
@import "tailwindcss";

/* Token bridge: maps every Carbon role to Tailwind utility classes. */
@import "@avala/design/styles";

/* Brand × tone: g10 (light) at :root, g100 (dark) under .dark. */
@import "@avala/design-tokens/css/avala.css";

/* — or any of the 8 brands —
@import "@avala/design-tokens/css/pay.css";
@import "@avala/design-tokens/css/learning.css";
@import "@avala/design-tokens/css/investor.css";
@import "@avala/design-tokens/css/tickets.css";
@import "@avala/design-tokens/css/operations.css";
@import "@avala/design-tokens/css/chat.css";
@import "@avala/design-tokens/css/neutral.css";
*/

next.config.ts

One helper sets up transpilePackages + webpack.resolve.modules so the kit's symlinked dist finds its transitive deps.

next.config.tstsx
import type { NextConfig } from "next";
import { withAvalaDesign } from "@avala/design/next-config";

const baseConfig: NextConfig = {
  // your config…
};

export default withAvalaDesign(baseConfig);

First component

src/app/page.tsxtsx
import { Button, Tag } from "@avala/design";

export default function Page() {
  return (
    <div className="p-8">
      <h1 className="text-heading-04 text-text-primary mb-4">Hello, Avala.</h1>
      <Button kind="primary" size="md">Primary action</Button>
      <Tag tone="green" className="ml-3">Stable</Tag>
    </div>
  );
}

Theme provider

Wraps your app, persists brand × tone to localStorage, toggles the .dark class on <html> so the dual-tone CSS swaps tokens.

src/app/layout.tsxtsx
import { ThemeProvider } from "@avala/design";
import "./globals.css";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en" suppressHydrationWarning>
      <body>
        <ThemeProvider defaultBrand="avala" defaultTone="g100">
          {children}
        </ThemeProvider>
      </body>
    </html>
  );
}

What's next