Skip to content

Getting started

Installation

Two steps: install the package, then wire Tailwind CSS v4 up to the library's classes and tokens.

Requirements

  • • React 18 or 19
  • • Tailwind CSS 4 — the library uses v4-only syntax and utilities; v3 is not supported
  • • Framer Motion 11+ — optional, only for the spring animation adapter

Install

terminal
pnpm add appshell-react
# optional, for spring animations:
pnpm add framer-motion

Tailwind v4 setup

appshell-react ships unstyled-by-convention: its components use Tailwind utility classes driven by shadcn/ui-style tokens, and your build generates the CSS. Your global stylesheet needs four things — the @source line, the dark-mode custom variant, the tokens, and the @theme inline mapping:

globals.css
@import "tailwindcss";

/* 1. Let Tailwind see the library's classes.
      Without this line the shell renders completely unstyled. */
@source "../node_modules/appshell-react/dist";

/* 2. Dark mode as a class (the library follows your tokens) */
@custom-variant dark (&:is(.dark *));

/* 3. Design tokens — shadcn/ui-style custom properties */
:root {
  --background: oklch(1 0 0);
  --foreground: oklch(0.145 0 0);
  --primary: oklch(0.205 0 0);
  --primary-foreground: oklch(0.985 0 0);
  --muted: oklch(0.97 0 0);
  --muted-foreground: oklch(0.556 0 0);
  --accent: oklch(0.97 0 0);
  --accent-foreground: oklch(0.205 0 0);
  --popover: oklch(1 0 0);
  --popover-foreground: oklch(0.145 0 0);
  --destructive: oklch(0.577 0.245 27.325);
  --destructive-foreground: oklch(0.985 0 0);
  --border: oklch(0.922 0 0);
  --ring: oklch(0.708 0 0);
  --radius: 0.625rem;
}

.dark {
  --background: oklch(0.145 0 0);
  --foreground: oklch(0.985 0 0);
  /* …dark values for the same tokens */
}

/* 4. Map tokens to Tailwind utilities (bg-background, text-foreground, …) */
@theme inline {
  --color-background: var(--background);
  --color-foreground: var(--foreground);
  --color-primary: var(--primary);
  --color-primary-foreground: var(--primary-foreground);
  --color-muted: var(--muted);
  --color-muted-foreground: var(--muted-foreground);
  --color-accent: var(--accent);
  --color-accent-foreground: var(--accent-foreground);
  --color-popover: var(--popover);
  --color-popover-foreground: var(--popover-foreground);
  --color-destructive: var(--destructive);
  --color-destructive-foreground: var(--destructive-foreground);
  --color-border: var(--border);
  --color-ring: var(--ring);
}

/* 5. Small utilities some components rely on */
@utility scrollbar-hide {
  -ms-overflow-style: none;
  scrollbar-width: none;
  &::-webkit-scrollbar {
    display: none;
  }
}
The @source line is the step everyone misses: Tailwind v4 only scans your own source by default, so without it none of the library’s classes are generated and the shell renders unstyled. If you use HeaderNav dropdowns, also install tw-animate-css (and import it) for the animate-in entrance utilities.

Mobile safe areas

Safe-area padding (notches, the Dynamic Island, gesture bars) is driven entirely by the platform standard env(safe-area-inset-*). For iOS and Android to report those values, your page must opt into edge-to-edge rendering with viewport-fit=cover:

viewport
<!-- index.html / your document head -->
<meta
  name="viewport"
  content="width=device-width, initial-scale=1, viewport-fit=cover"
/>

<!-- Next.js App Router: app/layout.tsx -->
export const viewport = {
  width: "device-width",
  initialScale: 1,
  viewportFit: "cover",
};
Skip this and every inset resolves to 0 — the shell still works, but pinned bars will sit under the status bar on real devices. See SafeArea for the details.

Optional: spring animations

Out of the box every transition is plain CSS. For spring-based reveal and drawer animations, wrap your app once:

app.tsx
import { MotionProvider } from "appshell-react";
import { framerMotionAdapter } from "appshell-react/motion-framer";

export default function App({ children }) {
  return (
    <MotionProvider adapter={framerMotionAdapter}>
      {children}
    </MotionProvider>
  );
}

Details and the adapter contract live in Motion.

Next steps

Set up your palette in Theming, or jump straight to the AppShell reference.