Start Next.js with the design system already in it

One command. Newest Next.js, colour generated from your brand HEX, motion tokens, and docs your agent reads. No Tailwind.

npx @larsen-utvikling/create-next-app my-app

Scaffolds Next.js 16.3.0 - resolved when you run it, never pinned

$npx @larsen-utvikling/create-next-app

  • What is your app named?my-app
  • Generate a custom 12-step palette from a single HEX?No
  • Which linter?ESLint
  • Install Larsen Skills for AI agents?Recommended
  • Which package manager?npm
  • Initialize a git repository?Yes
  • Install dependencies?Yes

Done. cd my-app && npm run dev

Why

Every project started with the same thirty minutes

Not hard work. Just the same work, slightly worse each time, because it was being re-derived from memory.

  • Delete the demo page and its CSSA welcome page that demonstrates the tokens
  • Copy spacing values from the last projectEight steps on a 4px base, decided
  • Pick colours, then pick them again for darkBoth modes generated from one HEX
  • Remember which easing curve you settled onFour curves, named for what moves
  • Rewrite AGENTS.md from memoryWritten, and matching the palette you chose
  • Add Tailwind, then fight itVanilla CSS custom properties

The flow

Seven questions. Every one has a flag.

Press enter through all of them for a considered default, or pass flags and skip the terminal entirely.

  1. What is your app named?

    • my-applowercase, digits, . _ -

    Becomes both the folder and the name in package.json, so it is checked for the characters those allow - and refuses a directory that already has files in it.

    pass the name as an argument
  2. Generate a custom 12-step palette from a single HEX?

    • Noship the Larsen Utvikling theme
    • Yesgenerate from your brand color

    Say no and you get a considered black-and-white theme with a blue accent. Say yes and three follow-up questions shape the whole palette.

    --hex <color> · omit it for the default theme
    • Enter your HEX color

      • #4DA0FFwith or without the #

      One color in. A 12-step accent scale, a 12-step gray scale, semantic colors and both modes out.

      --hex 4DA0FF
    • Choose framework/style

      • shadcn/uisemantic tokens + scales
      • Radix Colorsaccent + gray scales
      • CSS Variablesaccent + gray scales

      Decides which token names exist - and the generated docs and starter CSS are rewritten to match.

      --preset shadcn | radix | css-variables
    • Choose color format

      • HSL Values212 100% 65%
      • HEX
      • RGB
      • HSL
      • OKLAB
      • OKLCH

      HSL values keep alpha composition available. Any other format and the docs switch to the color-mix idiom instead.

      --format hsl-values | hex | rgb | hsl | oklab | oklch
  3. Which linter?

    • ESLint
    • Biome
    • None

    Passed straight through to create-next-app, so you get its official configuration for whichever you pick.

    --linter eslint | biome | none
  4. Install Larsen Skills for AI agents?

    • Recommendedmotion, interface, review, primitives
    • All9 skills
    • Let me pickmulti-select
    • No

    Installs into .agents/skills/ and symlinks each agent's own directory, so Claude Code, Codex, Cursor and Copilot all see them.

    --skills recommended | all | a,comma,list · --no-skills
  5. Which package manager?

    • npm
    • pnpm
    • yarn
    • bun

    Runs the install and rewrites the generated README so every command shown is the one you actually type.

    --pm npm | pnpm | yarn | bun
  6. Initialize a git repository?

    • Yesinit, add, first commit
    • No

    Commits everything after the overlay, so your first commit is the finished project rather than a bare template.

    --no-git
  7. Install dependencies?

    • Yes
    • No

    A missing package manager never fails the scaffold - you get the finished app and the command to finish it yourself.

    --no-install

Colour

One HEX in. A whole theme out.

The generator that runs during install runs here too, imported from the published package - so what you make is byte-for-byte what npx writes.

Framework / style
Colour format
Light
--background
--foreground
--primary
--accent-9
--muted
--border
Dark
--background
--foreground
--primary
--accent-9
--muted
--border
npx @larsen-utvikling/create-next-app my-app --hex 4DA0FF
Show the generated theme.css
src/lib/design-system/theme.css
/**
 * theme.css - color tokens
 *
 * Generated by @larsen-utvikling/create-next-app (rampkit engine).
 * Seed: #4DA0FF | preset: shadcn | format: hsl-values | scheme: analogous
 *
 * Dark mode follows the OS automatically. Set data-theme="light" or
 * data-theme="dark" on <html> to override manually - no JS shipped.
 * Regenerate any time by re-running the scaffolder, or edit values freely.
 */

:root {
  --background: 0 0% 95%;
  --foreground: 210 6% 13%;
  --foreground-subtle: 214 3% 44%;
  --primary: 212 100% 65%;
  --primary-foreground: 215 100% 22%;
  --secondary: 214 54% 90%;
  --secondary-foreground: 215 100% 22%;
  --muted: 210 3% 88%;
  --muted-foreground: 210 6% 13%;
  --accent: 214 54% 90%;
  --accent-foreground: 215 100% 22%;
  --destructive: 10 75% 48%;
  --destructive-foreground: 0 0% 100%;
  --border: 210 4% 73%;
  --input: 210 4% 73%;
  --ring: 212 100% 65%;
  --analogous: 242 78% 65%;
  --analogous-foreground: 0 0% 100%;
  --complementary: 35 100% 43%;
  --complementary-foreground: 210 6% 13%;
  /* Accent Scale - Light */
  --accent-1: 210 12% 94%;
  --accent-2: 212 33% 92%;
  --accent-3: 214 54% 90%;
  --accent-4: 212 82% 87%;
  --accent-5: 213 95% 84%;
  --accent-6: 213 95% 78%;
  --accent-7: 213 90% 72%;
  --accent-8: 212 90% 61%;
  --accent-9: 212 100% 65%;
  --accent-10: 212 87% 60%;
  --accent-11: 210 100% 35%;
  --accent-12: 215 100% 22%;
  /* Gray Scale - Light */
  --gray-1: 0 0% 93%;
  --gray-2: 210 5% 92%;
  --gray-3: 210 3% 88%;
  --gray-4: 220 4% 85%;
  --gray-5: 210 4% 81%;
  --gray-6: 204 4% 78%;
  --gray-7: 210 4% 73%;
  --gray-8: 210 5% 66%;
  --gray-9: 206 3% 48%;
  --gray-10: 214 3% 44%;
  --gray-11: 216 3% 33%;
  --gray-12: 210 6% 13%;
  /* Semantic Colors - Light */
  --success: 145 75% 52%;
  --success-foreground: 139 47% 18%;
  --success-muted: 133 40% 86%;
  --success-muted-foreground: 139 47% 18%;
  --success-border: 137 43% 58%;
  --danger: 10 75% 48%;
  --danger-foreground: 0 0% 100%;
  --danger-muted: 10 40% 89%;
  --danger-muted-foreground: 10 56% 23%;
  --danger-border: 11 59% 70%;
  --warning: 35 75% 50%;
  --warning-foreground: 0 0% 0%;
  --warning-muted: 31 91% 86%;
  --warning-muted-foreground: 33 69% 19%;
  --warning-border: 32 73% 60%;
  --info: 250 75% 51%;
  --info-foreground: 220 10% 94%;
  --info-muted: 231 47% 91%;
  --info-muted-foreground: 242 54% 29%;
  --info-border: 232 89% 79%;
}

@media (prefers-color-scheme: dark) {
  :root {
    --background: 0 0% 6%;
    --foreground: 210 6% 93%;
    --foreground-subtle: 210 2% 49%;
    --primary: 212 100% 65%;
    --primary-foreground: 212 54% 7%;
    --secondary: 212 66% 16%;
    --secondary-foreground: 212 100% 90%;
    --muted: 210 3% 13%;
    --muted-foreground: 210 6% 93%;
    --accent: 212 66% 16%;
    --accent-foreground: 212 100% 90%;
    --destructive: 10 75% 48%;
    --destructive-foreground: 0 0% 100%;
    --border: 210 3% 28%;
    --input: 210 3% 28%;
    --ring: 212 100% 65%;
    --analogous: 242 78% 65%;
    --analogous-foreground: 0 0% 100%;
    --complementary: 32 78% 65%;
    --complementary-foreground: 26 24% 6%;
    /* Accent Scale - Dark */
    --accent-1: 212 54% 7%;
    --accent-2: 213 42% 10%;
    --accent-3: 212 66% 16%;
    --accent-4: 210 94% 19%;
    --accent-5: 211 81% 25%;
    --accent-6: 211 67% 31%;
    --accent-7: 212 60% 38%;
    --accent-8: 212 60% 46%;
    --accent-9: 212 100% 65%;
    --accent-10: 212 87% 60%;
    --accent-11: 212 100% 73%;
    --accent-12: 212 100% 90%;
    /* Gray Scale - Dark */
    --gray-1: 210 6% 6%;
    --gray-2: 210 4% 10%;
    --gray-3: 210 3% 13%;
    --gray-4: 200 4% 16%;
    --gray-5: 210 2% 19%;
    --gray-6: 210 3% 23%;
    --gray-7: 210 3% 28%;
    --gray-8: 210 3% 38%;
    --gray-9: 210 3% 43%;
    --gray-10: 210 2% 49%;
    --gray-11: 210 3% 71%;
    --gray-12: 210 6% 93%;
    /* Semantic Colors - Dark */
    --success: 145 75% 52%;
    --success-foreground: 137 24% 6%;
    --success-muted: 136 41% 13%;
    --success-muted-foreground: 135 74% 82%;
    --success-border: 139 48% 28%;
    --danger: 10 75% 48%;
    --danger-foreground: 0 0% 100%;
    --danger-muted: 10 56% 14%;
    --danger-muted-foreground: 11 86% 89%;
    --danger-border: 10 51% 35%;
    --warning: 35 75% 50%;
    --warning-foreground: 26 23% 6%;
    --warning-muted: 31 43% 12%;
    --warning-muted-foreground: 31 84% 88%;
    --warning-border: 33 52% 28%;
    --info: 250 75% 51%;
    --info-foreground: 228 100% 92%;
    --info-muted: 239 49% 22%;
    --info-muted-foreground: 228 100% 92%;
    --info-border: 239 45% 47%;
  }
}

[data-theme="light"] {
  --background: 0 0% 95%;
  --foreground: 210 6% 13%;
  --foreground-subtle: 214 3% 44%;
  --primary: 212 100% 65%;
  --primary-foreground: 215 100% 22%;
  --secondary: 214 54% 90%;
  --secondary-foreground: 215 100% 22%;
  --muted: 210 3% 88%;
  --muted-foreground: 210 6% 13%;
  --accent: 214 54% 90%;
  --accent-foreground: 215 100% 22%;
  --destructive: 10 75% 48%;
  --destructive-foreground: 0 0% 100%;
  --border: 210 4% 73%;
  --input: 210 4% 73%;
  --ring: 212 100% 65%;
  --analogous: 242 78% 65%;
  --analogous-foreground: 0 0% 100%;
  --complementary: 35 100% 43%;
  --complementary-foreground: 210 6% 13%;
  /* Accent Scale - Light */
  --accent-1: 210 12% 94%;
  --accent-2: 212 33% 92%;
  --accent-3: 214 54% 90%;
  --accent-4: 212 82% 87%;
  --accent-5: 213 95% 84%;
  --accent-6: 213 95% 78%;
  --accent-7: 213 90% 72%;
  --accent-8: 212 90% 61%;
  --accent-9: 212 100% 65%;
  --accent-10: 212 87% 60%;
  --accent-11: 210 100% 35%;
  --accent-12: 215 100% 22%;
  /* Gray Scale - Light */
  --gray-1: 0 0% 93%;
  --gray-2: 210 5% 92%;
  --gray-3: 210 3% 88%;
  --gray-4: 220 4% 85%;
  --gray-5: 210 4% 81%;
  --gray-6: 204 4% 78%;
  --gray-7: 210 4% 73%;
  --gray-8: 210 5% 66%;
  --gray-9: 206 3% 48%;
  --gray-10: 214 3% 44%;
  --gray-11: 216 3% 33%;
  --gray-12: 210 6% 13%;
  /* Semantic Colors - Light */
  --success: 145 75% 52%;
  --success-foreground: 139 47% 18%;
  --success-muted: 133 40% 86%;
  --success-muted-foreground: 139 47% 18%;
  --success-border: 137 43% 58%;
  --danger: 10 75% 48%;
  --danger-foreground: 0 0% 100%;
  --danger-muted: 10 40% 89%;
  --danger-muted-foreground: 10 56% 23%;
  --danger-border: 11 59% 70%;
  --warning: 35 75% 50%;
  --warning-foreground: 0 0% 0%;
  --warning-muted: 31 91% 86%;
  --warning-muted-foreground: 33 69% 19%;
  --warning-border: 32 73% 60%;
  --info: 250 75% 51%;
  --info-foreground: 220 10% 94%;
  --info-muted: 231 47% 91%;
  --info-muted-foreground: 242 54% 29%;
  --info-border: 232 89% 79%;
}

[data-theme="dark"] {
  --background: 0 0% 6%;
  --foreground: 210 6% 93%;
  --foreground-subtle: 210 2% 49%;
  --primary: 212 100% 65%;
  --primary-foreground: 212 54% 7%;
  --secondary: 212 66% 16%;
  --secondary-foreground: 212 100% 90%;
  --muted: 210 3% 13%;
  --muted-foreground: 210 6% 93%;
  --accent: 212 66% 16%;
  --accent-foreground: 212 100% 90%;
  --destructive: 10 75% 48%;
  --destructive-foreground: 0 0% 100%;
  --border: 210 3% 28%;
  --input: 210 3% 28%;
  --ring: 212 100% 65%;
  --analogous: 242 78% 65%;
  --analogous-foreground: 0 0% 100%;
  --complementary: 32 78% 65%;
  --complementary-foreground: 26 24% 6%;
  /* Accent Scale - Dark */
  --accent-1: 212 54% 7%;
  --accent-2: 213 42% 10%;
  --accent-3: 212 66% 16%;
  --accent-4: 210 94% 19%;
  --accent-5: 211 81% 25%;
  --accent-6: 211 67% 31%;
  --accent-7: 212 60% 38%;
  --accent-8: 212 60% 46%;
  --accent-9: 212 100% 65%;
  --accent-10: 212 87% 60%;
  --accent-11: 212 100% 73%;
  --accent-12: 212 100% 90%;
  /* Gray Scale - Dark */
  --gray-1: 210 6% 6%;
  --gray-2: 210 4% 10%;
  --gray-3: 210 3% 13%;
  --gray-4: 200 4% 16%;
  --gray-5: 210 2% 19%;
  --gray-6: 210 3% 23%;
  --gray-7: 210 3% 28%;
  --gray-8: 210 3% 38%;
  --gray-9: 210 3% 43%;
  --gray-10: 210 2% 49%;
  --gray-11: 210 3% 71%;
  --gray-12: 210 6% 93%;
  /* Semantic Colors - Dark */
  --success: 145 75% 52%;
  --success-foreground: 137 24% 6%;
  --success-muted: 136 41% 13%;
  --success-muted-foreground: 135 74% 82%;
  --success-border: 139 48% 28%;
  --danger: 10 75% 48%;
  --danger-foreground: 0 0% 100%;
  --danger-muted: 10 56% 14%;
  --danger-muted-foreground: 11 86% 89%;
  --danger-border: 10 51% 35%;
  --warning: 35 75% 50%;
  --warning-foreground: 26 23% 6%;
  --warning-muted: 31 43% 12%;
  --warning-muted-foreground: 31 84% 88%;
  --warning-border: 33 52% 28%;
  --info: 250 75% 51%;
  --info-foreground: 228 100% 92%;
  --info-muted: 239 49% 22%;
  --info-muted-foreground: 228 100% 92%;
  --info-border: 239 45% 47%;
}

/* Document defaults - written for shadcn x hsl-values */
body {
  background: hsl(var(--background));
  color: hsl(var(--foreground));
}

::selection {
  background: hsl(var(--accent-3));
  color: hsl(var(--foreground));
}

hr {
  border-top-color: hsl(var(--border));
}

Dark mode

Both modes, zero JavaScript

Follows the OS, with an attribute for a manual override. No provider, no flash, no hydration cost.

Auto
prefers-color-scheme, straight from the stylesheet
Manual
data-theme="light | dark" on <html> wins over it
Contrast
checked in the test suite before every release
<html data-theme="light">

The design system

Five files. You have read all of them.

Small enough to hold in your head, complete enough that you are not inventing spacing values on day three.

core.css
Spacing, widths, radii, type, layering
theme.css
Colour for both modes, generated for your seed
motion.css
Durations, curves, and the reduced-motion contract
base.css
A reset, deliberately colour-free
index.css
The single import your globals.css needs
Spacing 8 steps · 4px base
--space-14
--space-28
--space-312
--space-416
--space-524
--space-632
--space-748
--space-864
Type unitless leading

Heading 1.1

Body copy at 1.5, so a paragraph that wraps to three lines still has room to breathe.

Label 0.05em

Radii 4 · 8 · 16 · pill

Motion

Durations named for what moves

Most starters ship no motion layer, so every project invents its own curves. These are the values larsenutvikling.no already runs.

Under 300ms
A 180ms menu reads as more responsive than a 400ms one
Four curves
In one file, so near-identical beziers cannot drift apart
Reduced motion
Distance and scale collapse; transitions keep running
--duration-press
:active140ms
--duration-fast
hover, exits160ms
--duration-ui
menus200ms
--duration-slow
modals240ms
--duration-enter
entrances300ms

Agent docs

The rules your agent reads before it writes

An agent that does not know your conventions invents them, differently every session. These ship with the project.

  • AGENTS.mdThe project rules every coding agent reads
  • CLAUDE.mdA one-line pointer
  • DESIGN.mdThe token reference
  • NEXTJS.mdNext.js's own agent guide, preserved
  • README.mdGetting started, with a post-scaffold checklist

AGENTS.md

Never Tailwind, the token idiom for your chosen palette, the motion rules, and the writing conventions.

Agent skills

Nine skills, installed into the project

Optional, and off by default when unattended. One install covers Claude Code, Codex, Cursor, Copilot and Gemini CLI.

Shared values
The motion tokens come from motion-craft, so tokens and guidance agree
Browse them
github.com/Stianlars1/larsen-skills
motion-craftDurations, curves, reduced motioninterface-craftLayout, hierarchy, spacing, typeinterface-reviewReview a screen against the rulesui-primitive-pickerThe right primitive for a patternmotion-vocabularyName an effect you can describeliquid-interfaceGlass and refraction effectsprototype-labThrowaway interaction prototypesreverse-engineer-motionRebuild motion from a videoanimated-logo-cycleAnimated brand marks

.agents/skills/

Unattended

It runs in CI, or tells you why it cannot

Without a terminal the CLI fails immediately and names the flag that answers the question, instead of hanging on a prompt nobody can see.

Everything default
npx @larsen-utvikling/create-next-app my-app --defaults
Your colour, your tools
npx @larsen-utvikling/create-next-app my-app \
  --hex 22C55E --preset shadcn \
  --pm pnpm --linter biome \
  --skills recommended
Every flag
-d, --defaultsSkip every prompt and take the defaults
--hex <color>Palette seed, with or without the #. Implies a custom palette
--preset <name>shadcn | radix | css-variables
--format <name>hex | rgb | hsl | hsl-values | oklab | oklch
--scheme <name>analogous | monochromatic | complementary | triadic
--linter <name>eslint | biome | none
--skills <list>recommended | all | comma-separated skill names
--no-skillsSkip the skills install
--pm <name>npm | pnpm | yarn | bun
--no-gitSkip git init
--no-installSkip the dependency install
--cna-version <spec>Pin create-next-app instead of taking the newest
-v, --versionPrint the version
-h, --helpShow help

On disk

What the first commit contains

Nothing to clean up first. The demo page is replaced, the unused assets are gone, and git is initialised with the finished project.

Next.js
Resolved at scaffold time, so the version is never stale
Import alias
@/* - the same default Next.js uses
NEXTJS.md
Next.js's own agent guide, preserved rather than overwritten
README
Carries a checklist of the things only you can do
my-app
my-app/
├── AGENTS.md          project rules for agents
├── CLAUDE.md          → @AGENTS.md
├── DESIGN.md          token reference
├── NEXTJS.md          Next.js agent guide, preserved
├── README.md          getting started + checklist
├── .agents/skills/    installed Larsen Skills
├── public/
└── src/
    ├── app/
    │   ├── layout.tsx
    │   ├── globals.css    one import line
    │   ├── page.tsx       welcome page, yours to replace
    │   └── page.css
    └── lib/design-system/
        ├── index.css
        ├── core.css       spacing · widths · radii · type · z
        ├── theme.css      color, light + dark
        ├── motion.css     durations · curves · reduced motion
        └── base.css       reset

Skills live in .agents/skills/ - see the collection.