Design System
Getting started

Installation

Major is one package, @major/ui: the components and the tokens they are built on, versioned together. Works with Next.js (App Router and Server Components), Vite and any Tailwind v4 setup.

1. Install the package

It is published to the company's private npm registry under the @major scope.
.npmrc
@major:registry=https://npm.major.example/
//npm.major.example/:_authToken=${MAJOR_NPM_TOKEN}
yarn add @major/ui lucide-react
# or: pnpm add @major/ui lucide-react
  • Keep the token in MAJOR_NPM_TOKEN, in your shell and in CI; never commit it.
  • Needs React 19 and Tailwind v4. lucide-react is for your own icons: components take icons as elements.

2. Load the styles

One import: the tokens (light and dark), the Tailwind theme, and a @source that lets your Tailwind generate the classes the components use.
app/globals.css
@import "tailwindcss";
@import "@major/ui/tailwind.css";

The theme resets Tailwind's palette and text sizes: only Major values exist (bg-surface, text-ink, text-body, rounded-xl…), in the components and in your own markup.

3. Load the fonts

DM Sans and JetBrains Mono, from Google Fonts. In Next.js let next/font self-host them and point the tokens at them.
app/layout.tsx
import { DM_Sans, JetBrains_Mono } from "next/font/google";
import type { PropsWithChildren } from "react";
import { Toaster } from "@major/ui/sonner";
import { TooltipProvider } from "@major/ui/tooltip";

const sans = DM_Sans({ subsets: ["latin"], variable: "--font-dm-sans" });
const mono = JetBrains_Mono({ subsets: ["latin"], variable: "--font-jetbrains-mono" });

export default function RootLayout({ children }: PropsWithChildren) {
  return (
    <html lang="en" className={`${sans.variable} ${mono.variable}`}>
      <body>
        <TooltipProvider>
          {children}
          <Toaster />
        </TooltipProvider>
      </body>
    </html>
  );
}
app/globals.css
:root {
  --font-sans: var(--font-dm-sans), "DM Sans", ui-sans-serif, system-ui, sans-serif;
  --font-mono: var(--font-jetbrains-mono), "JetBrains Mono", ui-monospace, monospace;
}

4. Use a component

Every component has its own entry point; each page starts its API section with the import.
import { Button } from "@major/ui/button";
import { Plus } from "lucide-react";

export function AgentsHeader() {
  return (
    <Button>
      <Plus />
      Create agent
    </Button>
  );
}
  • Components are client components ("use client" is in each file), so you can render them from Server Components. cn() and the tokens are not: use them on the server too.
  • Components are composed from parts, as in shadcn/ui: Dialog + DialogContent + DialogTitle, Field + FieldLabel + Input. Icons go in children like any other content.
  • Primitives (Button, Dialog, Select…) carry no product meaning; composed components (PageHeader, DataTable, Trace…) are built from them for one recurring Major need. The sidebar lists them separately.
  • Compose with cn() from @major/ui/lib/cn to merge your classes; it knows Major's theme (it won't drop text-ink when you add text-ui).
  • "Go to definition" opens the component's TypeScript source: the package ships declaration maps and sourcemaps with the sources inside.

When a component doesn't fit

A component that doesn't fit is either a bug in the system or a use it wasn't made for. Both are fixed in the system.
  • First compose: className, asChild and the exported variants (buttonVariants) cover most cases without changing the component.
  • If it is a bug, open an issue on the design system. To ship before the fix is released, patch the package with yarn patch @major/ui (or pnpm patch) and link the issue in the patch commit. The patch stays visible in review and fails loudly when an upgrade changes the same lines.
  • Never copy a component into a product to change it.

Adding a stock shadcn/ui component

The theme defines shadcn's variable names (background, primary, border, ring…) mapped onto Major tokens, so components from the shadcn registry pick up Major's look. If a product needs one long term, propose it for @major/ui.
pnpm dlx shadcn@latest add hover-card

Note: in Major, accent is the call-to-action green and muted is a text colour. Replace shadcn's bg-accent (hover) with bg-hover and bg-muted with bg-secondary.