Use it elsewhere

Claude Code

Spin up a Flatpay-native Next.js project inside Claude Code from a single terminal prompt. The template scaffolds Next 16 + Tailwind v4 + React 19, wires `@flatpay-dk/ui` and the v4 preset, and writes a `CLAUDE.md` at the repo root so every subsequent turn inherits the same system rules.

01 · The prompt

One prompt, every project

Claude Code reads project rules from a CLAUDE.md at the repo root on every turn — once it’s written, the rules persist without re-pasting. The template below installs the system, writes the rules file, and runs the project you describe at the bottom in one paste. Edit only the section under == Project ==.

markdown

Set up the Flatpay design system in this project, then build
the project I describe at the bottom.

== Setup ==

1. Scaffold (skip if the workspace already has a Next.js app):

   pnpm create next-app@latest .
     --typescript --eslint --app --tailwind --src-dir --turbopack
     --import-alias "@/*"

   Targets Next.js 16 (App Router), React 19, Tailwind v4. Use
   pnpm; the project's lockfile assumes it.

2. Install from GitHub Packages (FLATPAY-DK org). Assumes the
   user has already run a one-time `npm login --scope=@flatpay-dk
   --registry=https://npm.pkg.github.com` with a classic PAT
   (read:packages scope, SSO-authorized for FLATPAY-DK). If the
   install 401s, point them at the Getting started page on the
   docs site; don't try to authenticate yourself.

   Drop a `.npmrc` at the project root:

   @flatpay-dk:registry=https://npm.pkg.github.com

   Then:

   pnpm add @flatpay-dk/ui @flatpay-dk/tailwind-preset

3. In src/app/globals.css, replace the default Tailwind import
   with the preset chain. Tailwind v4 is CSS-first — there is no
   tailwind.config.{ts,js}; the preset wires every token and
   the .eyebrow utility classes through @theme:

   @import "tailwindcss";
   @import "@flatpay-dk/tailwind-preset";

   @source "../../node_modules/@flatpay-dk/ui/dist/**/*.js";

   html, body {
     background: var(--background);
     color: var(--foreground);
   }

   @layer base {
     * { border-color: var(--border); }
   }

4. Wire fonts in src/app/layout.tsx via the package's named
   exports — Inter Tight + Martian Mono ship as next/font/local
   instances. Apply them on <html> via their `.variable` strings
   so the Tailwind preset's `--font-sans` and `--font-mono` are
   resolved:

   import { interTight, martianMono } from "@flatpay-dk/ui/fonts";

   export default function RootLayout({ children }: { children: React.ReactNode }) {
     return (
       <html
         lang="en"
         className={`${interTight.variable} ${martianMono.variable}`}
         suppressHydrationWarning
       >
         <body className="bg-background text-foreground antialiased">
           {children}
         </body>
       </html>
     );
   }

   Don't reach for { useFlatpayFonts } from "@flatpay-dk/ui/fonts" —
   Next 16 rejects its compiled output ("Font loader calls must
   be assigned to a const"). The named imports above are the
   canonical Next 16 pattern and work in Next 15 too.

   Toggle dark by setting [data-theme="dark"] on <html>.

5. Write CLAUDE.md at the repo root with the system rules below.
   Claude Code re-reads this file on every turn, so the rules
   persist without me re-pasting them. If a CLAUDE.md already
   exists, append a "## Flatpay design system" section instead
   of overwriting.

== System rules ==
   (write into CLAUDE.md verbatim, under the "## Flatpay design
   system" section)

Use @flatpay-dk/ui as the only UI source — no shadcn,
no @/components/ui/* re-exports, no headless-ui, no Radix
imported directly. The package is the canonical layer.

Two scaffolds, used together. Get this wrong and the route
loads without the chrome.

  • NavigationPage — the app SHELL. Renders the top bar + side
    rail and gives the route a content slot. Lives in a
    layout file (e.g. src/app/(portal)/layout.tsx) so a
    section of routes shares one shell:

      // src/app/(portal)/layout.tsx
      "use client"; // sidebar mode is React state — the layout owns it
      import { useState } from "react";
      import {
        NavigationPage,
        NavigationTopBar,
        NavigationToggle,
        NavigationSideBar,
        NavigationSearch,
        NavigationItems,
      } from "@flatpay-dk/ui";

      export default function PortalLayout({ children }: { children: React.ReactNode }) {
        const [mode, setMode] = useState<"expanded" | "mini">("expanded");
        return (
          <NavigationPage
            topBar={
              <NavigationTopBar>
                <NavigationToggle
                  mode={mode}
                  onClick={() => setMode((m) => (m === "expanded" ? "mini" : "expanded"))}
                />
                {/* NavigationBrand, NavigationTopBarSpacer, utilities, NavigationLocation */}
              </NavigationTopBar>
            }
            sideBar={
              <NavigationSideBar
                mode={mode}
                activeHref={/* current path */}
                onExpandRequest={() => setMode("expanded")}
              >
                <NavigationSearch />
                <NavigationItems merchant="POS" />
              </NavigationSideBar>
            }
          >
            {children}
          </NavigationPage>
        );
      }

  • Page — the per-route SURFACE that lives inside the
    NavigationPage's content slot. Holds PageHeader +
    PageBody + optional PageStickyFooter:

      // src/app/(portal)/dashboard/page.tsx
      export default function DashboardPage() {
        return (
          <Page variant="main" width="default">
            <PageHeader title="Dashboard" description="…" actions={…} />
            <PageBody>{/* content */}</PageBody>
            <PageStickyFooter>{/* optional — same width as Page */}</PageStickyFooter>
          </Page>
        );
      }

Always wrap a route's content in <Page> — never compose a
screen out of <main>, raw <header>, or hand-rolled wrappers.
Page owns the H1 typography (Founders Grotesk X-Condensed →
Inter Tight fallback) and the column width.

Page variant rules:
  • variant="main" — landing surface inside a section. Lives
    inside NavigationPage. Never renders a back button (the
    side rail goes up the tree).
  • variant="subpage" — nested or detail page. backHref is
    required; the page itself renders the back affordance.
    Sits inside NavigationPage unless the route is meant to
    escape the chrome.
  • width — "default" (max-w-4xl), "narrow" (max-w-2xl),
    "full". PageStickyFooter inherits whichever you pick so
    the bar's actions land under the form's content column.

When NOT to render NavigationPage: routes that should escape
the portal chrome — sign-in, error pages, marketing landing
pages, focused checkout steps. Put those routes OUTSIDE the
section's layout-file group so they don't inherit the shell,
then render Page on its own. If the design says "no rail",
say "no rail" — don't fake it by hiding NavigationPage at
certain breakpoints.

Reach-for cheat sheet (every export ships from the root):
  • Page surface: Page (variant="main" | "subpage", width=
    "default" | "narrow" | "full"), PageHeader, PageBody,
    PageStickyFooter — the canonical scaffold for every route.
  • Actions: Button (variant="primary" | "secondary" |
    "tertiary" | "success" | "danger"), ButtonGroup, Link.
  • Status + feedback: Banner, Toast / ToastStack /
    ToastProvider / useToast, Badge, StatusBadge, Chip /
    ChipGroup.
  • Inputs: TextField, NumberField, Search, Combobox, Select,
    DatePicker, TimePicker, Checkbox, RadioGroup, Toggle,
    FileUpload, ChoiceList.
  • Surfaces: Card, Section, Hero, Drawer, Modal, Dialog,
    Popover, Tooltip, Collapsible, FAQ, Tabs.
  • Tables + data: Table, EditableTable, MetricCard /
    MetricsGroup, SummaryBox, Chart, Filters, Pagination.
  • Navigation: NavigationPage (the canonical portal shell — top
    bar + side rail are never used independently),
    NavigationTopBar, NavigationSideBar, NavigationItems
    (merchant: "PAY" | "POS" | "Ecom", default "POS"; admin
    flag prepends the back-office block), NavigationItem,
    NavigationSubItem, NavigationLocation (variant: "chip" |
    "secondary"), NavigationSearch, NavigationBrand,
    NavigationToggle, NavigationUtility, NavigationDivider.
  • Layout primitives: StickyFooter (type: "fixed" | "floating",
    maxWidth: "full" | "7xl" | "4xl") — match its maxWidth to
    the page's content column. Floating is reserved for table-
    row bulk actions.
  • Iconography: Icon wrapper + the FlatpayLogo / FlatpayMarque
    brand marks. Pair with Material Symbols Outlined glyphs.

Tokens are how colour and spacing reach the screen. Use the
Tailwind utilities the preset wires up — never hard-coded hex,
never raw numeric Tailwind shades:

  • Surfaces: bg-background, bg-card, bg-muted,
    bg-accent-neutral-subtlest / -subtler / -subtle (subtlest
    opens, subtler is a card on canvas, subtle is an inner
    highlight; never open on subtle), and the categorical
    accents — bg-accent-{blurple|green|orange|pink|blue|purple|
    yellow|red|natural}-subtlest.
  • Text: text-foreground, text-muted-foreground,
    text-accent-{hue}, text-success / text-warning /
    text-destructive / text-info / text-discovery.
  • Status: bg-success / bg-warning / bg-destructive / bg-info /
    bg-discovery — pair with their *-foreground siblings.
  • Borders + outline: border, border-outline,
    border-outline-hover, ring-ring (focus). Default border is
    var(--border).
  • Spacing: 4-pt scale via Tailwind utilities (gap-1 → gap-24).

When to compose, when to extend, when to author new

  • Compose first. 80% of screens are existing components
    arranged via the system's spacing scale.
  • Extend second. If a canonical component covers 90% of the
    job but a prop is missing, extend the existing component
    (additive API, default to current behaviour) and contribute
    the change back upstream via a PR + changeset against
    FLATPAY-DK/lab-ui. Don't fork inline.
  • Author new only when the pattern doesn't exist AND the
    brief is primitive enough to be reusable. Build it from
    @flatpay-dk/ui primitives + token utilities. If three
    pages reach for it, upstream it.
  • Never copy a canonical component's source into the app to
    "tweak" it — that fragments the system. Use className
    overrides or asChild slots, or extend upstream.

Don't import @flatpay-dk/ui/tailwind-v3 (the legacy v3 preset
for Lovable / Vite). Don't reach for shadcn-style
`@/components/ui/*` paths. Don't try to install Founders
Grotesk X.

Docs: https://design.flatpay.dev/

== Project ==

[Replace this line with what you want built — what the screen
is, who it's for, which Flatpay components to reach for. See
the example below this template if you need a shape to copy.]

On CLAUDE.md persistence

Claude Code reads CLAUDE.md from the repo root (and from every parent directory) on every turn. Once the system-rules block is written there, you don’t need to re-paste it for follow-up prompts — just describe the next thing to build. If the rules ever drift, ask Claude to re-read CLAUDE.md before continuing.

02 · Example

What goes in the project slot

The body Claude Code actually builds from. Drop something like this into the == Project == section of the template above. Name the screen, name the audience, then point at the components you expect.

markdown

Build the Payouts page in the merchant portal at /payouts.
This is the route a logged-in merchant lands on when they
click "Payouts" in the portal's side rail. The page shows
their payouts across a chosen date range — total collected,
surcharges, net collected, fees, Capital repayments, and
net payout per period — and exports the same ledger to PDF
or XLS for reconciliation against the merchant's accounting
software.

This is a "main" page surface — render the FULL portal
chrome. NavigationPage wraps the route via the section's
layout file (src/app/(portal)/layout.tsx) so /payouts and
every other portal route share one shell — top bar + side
rail render once and don't remount on navigation. The
/payouts route's own page.tsx wraps its content in <Page
variant="main" width="full">. The active rail item is
/payouts.

NavigationTopBar contents (left → right):
  • NavigationToggle — the 48 × 48 collapse-rail button at
    the leading edge. Its mode prop and onClick are wired
    to the same useState hook in the layout that drives
    NavigationSideBar's mode prop. Clicking flips the rail
    between "expanded" (272 px, labels visible) and "mini"
    (80 px, icon-only). Without this control there's no way
    to collapse the rail — never omit it.
  • NavigationBrand (Flatpay logo).
  • NavigationTopBarSpacer.
  • NavigationUtility ariaLabel="What's new" tone="yellow"
    (icon: campaign).
  • NavigationUtility ariaLabel="Downloads" (icon:
    file_download).
  • NavigationUtility ariaLabel="Help" (icon: help_outline).
  • NavigationUtility ariaLabel="Account" (icon:
    account_circle).
  • NavigationLocation merchant="Ristorantes Argentinos
    Streetfood ApS" location="Berlin West" (truncates with
    ellipsis when the merchant name overflows).

NavigationSideBar in expanded mode, activeHref="/payouts".
Use <NavigationItems merchant="POS" /> for the canonical
row stack — Dashboard, Transactions, Payouts (with
indicator="dot"), Reports (chevron), Devices (chevron),
Staff (chevron), Online (badge "New"), Capital (badge
"New"), Accounting, Settings (chevron), My business
(chevron). NavigationSearch sits above the list with
placeholder "Search the portal…".

Inside the Page, build:

1. PageHeader.
   • title: "Payouts".
   • description: "Track your payouts across any period
     and export the data straight to your accounting
     software for reconciliation."
   • No actions slot here — the export action lives in the
     toolbar below so it sits next to the date range it
     operates on.

2. PageBody — single column with three stacked blocks:

   a. Toolbar row (h-12, gap-3 between left-cluster items).
      Left cluster:
        • Date-range stepper — left chevron Button
          (variant="tertiary", icon-only), a 14 px
          semibold "8 Feb 2024" label centred between
          chevrons, right chevron Button. Clicking either
          chevron shifts the visible range by one calendar
          day / week / month.
        • Select with the value "Calendar Day" (granularity
          options: Calendar Day, Calendar Week, Calendar
          Month).
        • Icon-only Button (variant="tertiary") with a
          "view_column" Material Outlined glyph — opens a
          column-customizer popover that toggles each
          table column's visibility.
      Right edge:
        • Button variant="primary" with a leading
          file_download icon — label "Export". Clicking
          opens a Menu with two MenuItems, "Payouts PDF"
          and "Payouts XLS", both wired to the export
          endpoint with the current date range pre-applied.

   b. MetricsGroup with five MetricCards (responsive: 2-up
      below md, 3-up below lg, 5-up at lg+). bg-card, no
      border, no chart sparkline. Eyebrow + value, in
      order:
        • Transactions    — "5.981"
        • Total collected — "32.198,00 €"
        • Net collected   — "31.872,30 €"
        • Total fees      — "725,40 €"
        • Net payout      — "31.146,90 €"
      Values render with font-variant-numeric: tabular-nums
      slashed-zero so digits line up as the numbers update.

   c. Table (canonical Table from the package, density
      "compact", sticky header, no row selection — this is
      a read-only ledger). Columns left → right:
        • Date              — left-aligned, "2024-01-27".
        • Period            — text-muted-foreground,
          "01-22 to 01-24".
        • Total collected   — right-aligned, tabular-nums.
        • Surcharges        — right-aligned, tabular-nums.
          Header carries a help_outline glyph wrapped in a
          Tooltip: "Surcharges are extra fees charged on
          certain card schemes; passed through to the
          merchant." Empty cells render an em-dash "—" in
          text-muted-foreground.
        • Net collected     — right-aligned.
        • Fees              — right-aligned.
        • Capital repayments — right-aligned.
        • Net payout        — right-aligned, font-semibold.
      Pagination at the bottom uses the canonical
      Pagination component.

      Seed eight rows that mirror the screenshot:
        2024-01-27 | 01-22 to 01-24 | 6.615,30 € | 20,67 € |
          6.594,63 € | 96,82 € | 1.096,82 € | 6.401,18 €
        2024-01-22 | 01-18 to 01-21 | 5.337,02 € | 11,27 € |
          5.326,67 € | 85,23 € | 985,23 €  | 5.241,72 €
        2024-01-20 | 01-15 to 01-20 | 8.214,58 € | 20,67 € |
          8.012,53 € | 123,82 €| 1.123,82 € | 7.293,68 €
        2024-01-15 | 01-13 to 01-14 | 5.337,02 € | —       |
          5.326,67 € | 85,23 € | 1.085,23 € | 5.241,72 €
        2024-01-13 | 01-08 to 01-12 | 6.641,89 € | —       |
          6.641,89 € | 103,23 €| 1.103,23 € | 6.486,88 €
        2024-01-08 | 01-06 to 01-07 | 8.214,58 € | 12,23 € |
          8.012,53 € | 123,82 €| 1.123,82 € | 7.293,68 €
        2024-01-06 | 01-03 to 01-05 | 5.337,02 € | —       |
          5.326,67 € | 85,23 € | 885,23 €  | 5.241,72 €
        2024-01-02 | 12-29 to 01-02 | 6.641,89 € | —       |
          6.641,89 € | 103,23 €| 1.103,23 € | 6.486,88 €

Mobile (below md) adaptations:
  • PageHeader collapses to title + description plus a
    single trailing icon-Button (file_download glyph) that
    opens the same Payouts PDF / XLS menu.
  • Toolbar: drop the column-customizer Button; keep the
    date-range stepper and granularity Select.
  • Above the metrics, add a Search input + a Filters
    trigger (icon-only Button with a count Badge) so the
    merchant can scope a long ledger on a small screen.
  • MetricsGroup reflows to 2 columns × 3 rows; add a
    "Pending" card (e.g. payouts in flight) to fill the
    sixth slot.
  • Table collapses to two columns: Date — with the
    period range as a small caption beneath the date — on
    the left; Net payout (with a Badge for status:
    "Completed" / "Pending" / "Failed") and a chevron-
    right glyph on the right. Each row links to the row's
    detail subpage at /payouts/[id]. Use a compact
    Pagination variant: "1 of 1" with prev/next chevrons.

Wire colours through canonical tokens:
  • bg-background for the page surface; bg-card for the
    metric tiles.
  • text-foreground for headings and primary cell values,
    text-muted-foreground for secondary cells (Period,
    placeholder em-dashes), text-success-foreground for
    the Completed status Badge on mobile.
  • All numeric cells use tabular-nums slashed-zero so
    columns align across rows regardless of digit width.

No hard-coded hex. No raw <main>, <header>, or hand-rolled
wrappers. Compose through NavigationPage + Page + the
canonical components.

Two checks once the work lands. Every UI import should come from @flatpay-dk/ui (no @/components/ui/... shadcn paths, no inline copies of canonical components), and Tailwind utilities should resolve through tokens like bg-accent-blurple-subtlest and text-foreground. If imports come back as @/components/ui/..., confirm CLAUDE.md actually has the system-rules section and ask Claude to re-read it.

What’s next

Beyond the wire-up

  • InstallGetting started — the same package wired into a hand-rolled Next.js project, useful as a sandbox to compare the generated output against the canonical install.
  • ExtendContributing — when a Claude Code session produces a component you want to keep, this is how it lands back in @flatpay-dk/ui.
  • ReferenceClaude Code · Memory & CLAUDE.md — Anthropic’s own guidance on how the rules file is read, scoped, and inherited from parent directories.