Quickstart

Add quadratic/ui to your Next.js app.

Create a Next.js app with TypeScript and Tailwind CSS. I recommend using create-t3-app with pnpm. Check out my detailed guide on create-t3-app. Otherwise, you can follow the Next.js installation instructions.

Add the following dependencies to your project.

pnpm add tailwindcss-animate class-variance-authority clsx tailwind-merge

Add the Lucide icon library to your project.

pnpm add lucide-react

Make the following changes to your tailwind.config.ts file.

Add the darkMode property to the root of your config.

export default {
  darkMode: ["class"],
  ...
} satisfies Config;

Add the borderRadius, fontSize, lineHeight, and screens object to theme.

export default {
  ...
  theme: {
    borderRadius: {
      none: "0",
      px: "0.0625rem",
      "0.5": "0.125rem",
      1: "0.25rem",
      "1.5": "0.375rem",
      2: "0.5rem",
      "2.5": "0.625rem",
      3: "0.75rem",
      "3.5": "0.875rem",
      4: "1rem",
      5: "1.25rem",
      6: "1.5rem",
      7: "1.75rem",
      8: "2rem",
      9: "2.25rem",
      10: "2.5rem",
      11: "2.75rem",
      12: "3rem",
      13: "3.25rem",
      14: "3.5rem",
      15: "3.75rem",
      16: "4rem",
      full: "9999px",
    },
    fontSize: {
      2.5: ["0.625rem", "0.75rem"],
      3: ["0.75rem", "1rem"],
      3.5: ["0.875rem", "1.25rem"],
      4: ["1rem", "1.5rem"],
      4.5: ["1.125rem", "1.75rem"],
      5: ["1.25rem", "1.75rem"],
      6: ["1.5rem", "2rem"],
      7: ["1.75rem", "2.25rem"],
      8: ["2rem", "2.5rem"],
      9: ["2.25rem", "2.75rem"],
      10: ["2.5rem", "3rem"],
      11: ["2.75rem", "3.5rem"],
      12: ["3rem", "4rem"],
      13: ["3.25rem", "4.5rem"],
      14: ["3.5rem", "5rem"],
      15: ["3.75rem", "5.5rem"],
      16: ["4rem", "6rem"],
    },
    lineHeight: {
      5: "1.25rem",
      5.5: "1.375rem",
      6: "1.5rem",
      7: "1.75rem",
      8: "2rem",
      9: "2.25rem",
      10: "2.5rem",
      11: "2.75rem",
      12: "3rem",
      13: "3.25rem",
      14: "3.5rem",
      15: "3.75rem",
      16: "4rem",
    },
    screens: {
      "3xs": "300px",
      "2xs": "360px",
      xs: "480px",
      sm: "640px",
      md: "768px",
      lg: "840px",
      xl: "1024px",
      "2xl": "1280px",
      "3xl": "1400px",
      "4xl": "1600px",
      "5xl": "1800px",
    },
    ...
  },
  ...
} satisfies Config;

Under theme.extend, add this colors object.

export default {
  ...
  theme: {
    ...
    extend: {
      colors: {
        border: "hsl(var(--border))",
        input: "hsl(var(--input))",
        ring: "hsl(var(--ring))",
        background: "hsl(var(--background))",
        foreground: "hsl(var(--foreground))",
        primary: {
          DEFAULT: "hsl(var(--primary))",
          foreground: "hsl(var(--primary-foreground))",
        },
        secondary: {
          DEFAULT: "hsl(var(--secondary))",
          foreground: "hsl(var(--secondary-foreground))",
        },
        destructive: {
          DEFAULT: "hsl(var(--destructive))",
          foreground: "hsl(var(--destructive-foreground))",
          border: "hsl(var(--destructive-border))",
        },
        warning: {
          DEFAULT: "hsl(var(--warning))",
          foreground: "hsl(var(--warning-foreground))",
          border: "hsl(var(--warning-border))",
        },
        success: {
          DEFAULT: "hsl(var(--success))",
          foreground: "hsl(var(--success-foreground))",
          border: "hsl(var(--success-border))",
        },
        info: {
          DEFAULT: "hsl(var(--info))",
          foreground: "hsl(var(--info-foreground))",
          border: "hsl(var(--info-border))",
        },
        muted: {
          DEFAULT: "hsl(var(--muted))",
          foreground: "hsl(var(--muted-foreground))",
        },
        accent: {
          DEFAULT: "hsl(var(--accent))",
          foreground: "hsl(var(--accent-foreground))",
        },
        popover: {
          DEFAULT: "hsl(var(--popover))",
          foreground: "hsl(var(--popover-foreground))",
        },
        card: {
          DEFAULT: "hsl(var(--card))",
          foreground: "hsl(var(--card-foreground))",
        },
      },
    },
  },
} satisfies Config;

Add the tailwindcss-animate plugin to root of the config.

export default {
  ...
  plugins: [require("tailwindcss-animate")],
} satisfies Config;

Add the following colors and styles to your styles/globals.css file.

@tailwind base;
@tailwind components;
@tailwind utilities;

@layer base {
  :root {
    --background: 0 0% 100%;
    --foreground: 240 10% 4%;

    --primary: 240 6% 10%;
    --primary-foreground: 0 0% 98%;

    --secondary: 240 5% 96%;
    --secondary-foreground: 240 6% 10%;

    --muted: 240 5% 96%;
    --muted-foreground: 240 4% 46%;

    --accent: 240 5% 96%;
    --accent-foreground: 240 6% 10%;

    --popover: 0 0% 100%;
    --popover-foreground: 240 10% 4%;

    --card: 0 0% 100%;
    --card-foreground: 240 10% 4%;

    --destructive: 0 100% 97%;
    --destructive-foreground: 0 100% 45%;
    --destructive-border: 358 100% 94%;

    --warning: 48 100% 97%;
    --warning-foreground: 31 92% 45%;
    --warning-border: 49 91% 91%;

    --success: 145 81% 96%;
    --success-foreground: 140 100% 27%;
    --success-border: 146 91% 91%;

    --info: 208 100% 97%;
    --info-foreground: 210 92% 45%;
    --info-border: 221 91% 91%;

    --border: 240 6% 90%;
    --input: 240 6% 90%;
    --ring: 240 5% 65%;

    --chart-1: 173 58% 39%;
    --chart-2: 12 76% 61%;
    --chart-3: 197 37% 24%;
    --chart-4: 43 74% 66%;
    --chart-5: 27 87% 67%;
  }

  .dark {
    --background: 240 10% 4%;
    --foreground: 0 0% 98%;

    --primary: 0 0% 98%;
    --primary-foreground: 240 6% 10%;

    --secondary: 240 4% 16%;
    --secondary-foreground: 0 0% 98%;

    --muted: 240 4% 16%;
    --muted-foreground: 240 5% 65%;

    --accent: 240 6% 10%;
    --accent-foreground: 0 0% 98%;

    --popover: 240 10% 4%;
    --popover-foreground: 0 0% 98%;

    --card: 240 10% 4%;
    --card-foreground: 0 0% 98%;

    --destructive: 358 76% 10%;
    --destructive-foreground: 358 100% 81%;
    --destructive-border: 357 90% 16%;

    --warning: 64 100% 6%;
    --warning-foreground: 46 87% 65%;
    --warning-border: 60 100% 12%;

    --success: 149 100% 6%;
    --success-foreground: 150 87% 65%;
    --success-border: 148 100% 12%;

    --info: 215 100% 6%;
    --info-foreground: 216 87% 65%;
    --info-border: 223 100% 12%;

    --border: 240 4% 16%;
    --input: 240 4% 16%;
    --ring: 240 5% 84%;

    --chart-1: 220 70% 50%;
    --chart-2: 340 75% 55%;
    --chart-3: 30 80% 55%;
    --chart-4: 280 65% 60%;
    --chart-5: 160 60% 45%;
  }

  * {
    @apply box-border border-border;
  }

  body {
    @apply bg-background font-sans text-foreground;
    font-feature-settings:
      "rlig" 1,
      "calt" 1;
  }
}

Add a cn helper to utils/tailwind.ts. It's a utility function used to efficiently merge Tailwind CSS classes in JS without having sylte conflicts.

import { clsx, type ClassValue } from "clsx";
import { extendTailwindMerge } from "tailwind-merge";

import config from "../../tailwind.config";

const twMerge = extendTailwindMerge({
  extend: {
    classGroups: {
      "font-size": Object.keys(config.theme.fontSize).map(
        (key) => `text-${key}`,
      ),
      "text-color": Object.keys(config.theme.extend.colors).map(
        (key) => `text-${key}`,
      ),
      rounded: Object.keys(config.theme.borderRadius).map(
        (key) => `rounded-${key}`,
      ),
      "border-color": Object.keys(config.theme.extend.colors).map(
        (key) => `border-${key}`,
      ),
    },
  },
});

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}

That's it!