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.5rem"],
      10: ["2.5rem", "2.75rem"],
      11: ["2.75rem", "1"],
      12: ["3rem", "1"],
      13: ["3.25rem", "1"],
      14: ["3.5rem", "1"],
      15: ["3.75rem", "1"],
      16: ["4rem", "1"],
    },
    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))",
        },
        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))",
        },
        highlight: {
          DEFAULT: "hsl(var(--highlight))",
          foreground: "hsl(var(--highlight-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 {
    --highlight: 234 100% 98%;
    --highlight-foreground: 205 61% 28%;

    --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 93% 94%;
    --destructive-foreground: 0 70% 35%;
    --destructive-border: 0 91% 71%;

    --warning: 55 97% 88%;
    --warning-foreground: 32 81% 29%;
    --warning-border: 48 96% 53%;

    --success: 141 84% 93%;
    --success-foreground: 143 64% 24%;
    --success-border: 142 69% 58%;

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

  .dark {
    --highlight: 205 18% 15%;
    --highlight-foreground: 205 62% 86%;

    --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: 0 70% 35%;
    --destructive-foreground: 0 93% 94%;
    --destructive-border: 0 91% 71%;

    --warning: 32 81% 29%;
    --warning-foreground: 55 97% 88%;
    --warning-border: 48 96% 53%;

    --success: 143 64% 24%;
    --success-foreground: 141 84% 93%;
    --success-border: 142 69% 58%;

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

@layer base {
  * {
    @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. This makes it easier to add conditional Tailwind CSS classes.

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

const textColorClasses = [
  {
    text: [
      "highlight",
      "highlight-foreground",
      "background",
      "foreground",
      "muted",
      "popover",
      "muted-foreground",
      "popover-foreground",
      "border",
      "input",
      "card",
      "card-foreground",
      "primary",
      "primary-foreground",
      "secondary",
      "secondary-foreground",
      "accent",
      "accent-foreground",
      "destructive",
      "destructive-foreground",
      "destructive-border",
      "warning",
      "warning-foreground",
      "warning-border",
      "success",
      "success-foreground",
      "success-border",
      "ring",
    ],
  },
];

const borderRadiusClasses = [
  {
    rounded: [
      "none",
      "px",
      "0.5",
      "1",
      "1.5",
      "2",
      "2.5",
      "3",
      "4",
      "5",
      "6",
      "7",
      "8",
      "9",
      "10",
      "11",
      "12",
      "13",
      "14",
      "15",
      "16",
      "full",
    ],
  },
];

const fontSizeClassGroup = [
  {
    text: [
      "3",
      "3.5",
      "4",
      "4.5",
      "5",
      "6",
      "7",
      "8",
      "9",
      "10",
      "11",
      "12",
      "13",
      "14",
      "15",
      "16",
    ],
  },
];

const customTwMerge = extendTailwindMerge<
  "text-color" | "font-size" | "border-radius"
>({
  extend: {
    classGroups: {
      "text-color": textColorClasses,
      "font-size": fontSizeClassGroup,
      "border-radius": borderRadiusClasses,
    },
  },
});

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

That's it!