Install

One package and one stylesheet. React 19 is all it asks of your app.

Install the package

npm install alpenglow

It brings no dependencies of its own. React and React DOM are peers — your app already has them — and the icons are installed separately, below, so an app carries only the ones it uses.

Import the stylesheet once

Every component is painted by alpenglow/styles.css, tokens included. Import it once, where the app starts: the root layout in Next.js, the entry file in Vite. The components inject nothing themselves, so without this import they render unstyled.

// app/layout.tsx, or src/main.tsx
import 'alpenglow/styles.css';

With Tailwind, import it into a layer instead. Tailwind says why.

Load the font

The type is Inter, and the package does not ship it: the stylesheet names the family and falls back to the system sans if nothing has loaded it. Load it under the name Inter — with next/font, which declares that family name, or with your own @font-face — in the four weights the system uses.

// app/layout.tsx
import { Inter } from 'next/font/google';

const inter = Inter({ subsets: ['latin'], weight: ['400', '500', '600', '700'], display: 'swap' });

export default function RootLayout({ children }) {
  return (
    <html lang="en" className={inter.className}>
      <body>{children}</body>
    </html>
  );
}

The stack, for reference: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif.

Use the components

import { Button, Field, Input } from 'alpenglow';

export function Invite() {
  return (
    <form>
      <Field label="Email" description="One message, sent today.">
        <Input type="email" name="email" autoComplete="email" />
      </Field>
      <Button type="submit">Send invite</Button>
    </form>
  );
}

Check it

Render one button. It should look like this — a capsule in the accent, with a semibold label — and it should change with your system’s appearance if you have not chosen a theme. If it is a grey rectangle, the stylesheet import is missing. If the letters look like your operating system’s, the font has not loaded.

Server Components

Every component that needs the browser says so with 'use client', and the package keeps the directive, so a server page can render any of them. What a server page cannot do is hand a component a function, because React cannot send one from the server. The props that take one — DropdownMenu's trigger, a Table column's cell, any onSelect or onChange — belong in a client component of your own.

'use client';

import { useRouter } from 'next/navigation';
import { Button, DropdownMenu } from 'alpenglow';

export function RowActions({ id }: { id: string }) {
  const router = useRouter();
  return (
    <DropdownMenu
      trigger={(props) => (
        <Button variant="ghost" tone="neutral" {...props}>
          Actions
        </Button>
      )}
      items={[{ id: 'edit', label: 'Edit', onSelect: () => router.push(`/rows/${id}`) }]}
    />
  );
}

Icons

Fifteen icons were drawn for this system and ship with it. Everything else is IBM Carbon, installed on its own so an app carries only the icons it uses. Icons lists the drawn ones, and the Carbon names that mislead.

npm install @carbon/icons-react

import { AiSparkle } from 'alpenglow';
import { Search } from '@carbon/icons-react';

Dark mode

Nothing to install. The stylesheet follows prefers-color-scheme on its own, and one attribute on the root, data-theme, overrides it. Dark mode has the attribute, the script that applies a stored choice before the first paint, and what changes with the light.

What the package holds

ImportHolds
alpenglowThe components, the drawn icons, and the tokens in TypeScript.
alpenglow/styles.cssThe tokens and every component's styles. What almost every app imports.
alpenglow/tokens.cssThe custom properties alone, for the palette without the components.
alpenglow/tailwind-theme.cssThe tokens as Tailwind v4 utilities, and a dark: that follows them.

Tokens in TypeScript

The values the stylesheet uses are exported for code that computes with them — a chart choosing a label colour, a test asserting contrast. The function below is the one every number on this site is computed with.

import { tokenContrast } from 'alpenglow';

tokenContrast('text/secondary', 'surface/raised', 'dark'); // 10.51