export const title = "TanStack Start integration" export const description = "Set up a TanStack Start project with Untitled UI React in seconds. Our TanStack Start starter kit includes providers, routing, and everything you need to get started." ## Installation The easiest way to get started with Untitled UI in a TanStack Start project is to use our CLI: Using our CLI, you can initialize a new TanStack Start project with Untitled UI with the following command: ```bash npx untitledui@latest init untitled-ui --tanstack ``` While running the command, you'll be asked which color to use as the brand color: ```bash ? Which color would you like to use as the brand color? › - Use arrow-keys. Return to submit. ❯ brand red orange amber yellow lime green ... zinc neutral stone ``` This will create a new TanStack Start project in the `untitled-ui` directory with all the necessary configurations and components pre-installed. The choices are Tailwind's built-in palettes; picking one points the `--color-brand-*` variables in `theme.css` at it, and `brand` keeps the default purple. You can change the brand color at any time later (see [Theming](/docs/theming)). If you leave out the directory name, the CLI asks for it first. Once it's done, start the development server, which serves the app at `http://localhost:3000`: ```bash cd untitled-ui npm run dev ``` Great! You're all set to start using Untitled UI components. If something is missing, you can copy/paste what you need into your project directly from individual [components](/components) pages. **Need help?** Check our [GitHub repository](https://github.com/untitleduico/react/) for examples, or [open an issue](https://github.com/untitleduico/react/issues/) if you run into any problems. Our community is here to help! ## Manual installation If you prefer to add Untitled UI to an existing TanStack Start project, you can follow these steps: The TanStack Start template already includes `tailwindcss` and `@tailwindcss/vite`, so install the remaining packages with your favorite package manager: ```bash npm install @untitledui/icons react-aria-components tailwindcss-react-aria-components tailwind-merge tailwindcss-animate ``` ```bash yarn add @untitledui/icons react-aria-components tailwindcss-react-aria-components tailwind-merge tailwindcss-animate ``` ```bash bun add @untitledui/icons react-aria-components tailwindcss-react-aria-components tailwind-merge tailwindcss-animate ``` TanStack Start runs on Vite, and the template's `vite.config.ts` already has everything Untitled UI needs: ```ts import { defineConfig } from 'vite' import { devtools } from '@tanstack/devtools-vite' import { tanstackStart } from '@tanstack/react-start/plugin/vite' import viteReact from '@vitejs/plugin-react' import tailwindcss from '@tailwindcss/vite' const config = defineConfig({ resolve: { tsconfigPaths: true }, plugins: [devtools(), tailwindcss(), tanstackStart(), viteReact()], }) export default config ``` This configuration does two important things: 1. Adds the Tailwind CSS Vite plugin for efficient processing 2. Resolves the `@/` path alias from your `tsconfig.json` If your project was set up differently, make sure both are in place. The template's `tsconfig.json` already maps `@/*` to `./src/*`. Change `lib` from `ES2022` to `ESNext`, because some components use newer JavaScript methods such as `Array.prototype.toReversed()`: ```json { "compilerOptions": { // other options... "lib": ["ESNext", "DOM", "DOM.Iterable"], "paths": { "#/*": ["./src/*"], "@/*": ["./src/*"] } } } ``` Create a `theme.css` file in `src/styles` and add the following code: {/* @embed:start file="styles/theme.css" lang="css" */} ```css @theme { /* FONT FAMILY */ --font-body: var(--font-inter, "Inter"), -apple-system, "Segoe UI", Roboto, Arial, sans-serif; --font-display: var(--font-inter, "Inter"), -apple-system, "Segoe UI", Roboto, Arial, sans-serif; --font-mono: ui-monospace, "Roboto Mono", SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; /* FONT SIZE */ --text-xs: calc(var(--spacing) * 3); --text-xs--line-height: calc(var(--spacing) * 4.5); --text-sm: calc(var(--spacing) * 3.5); --text-sm--line-height: calc(var(--spacing) * 5); --text-md: calc(var(--spacing) * 4); --text-md--line-height: calc(var(--spacing) * 6); --text-lg: calc(var(--spacing) * 4.5); --text-lg--line-height: calc(var(--spacing) * 7); --text-xl: calc(var(--spacing) * 5); --text-xl--line-height: calc(var(--spacing) * 7.5); --text-display-xs: calc(var(--spacing) * 6); --text-display-xs--line-height: calc(var(--spacing) * 8); --text-display-sm: calc(var(--spacing) * 7.5); --text-display-sm--line-height: calc(var(--spacing) * 9.5); --text-display-md: calc(var(--spacing) * 9); --text-display-md--line-height: calc(var(--spacing) * 11); --text-display-md--letter-spacing: -0.72px; --text-display-lg: calc(var(--spacing) * 12); --text-display-lg--line-height: calc(var(--spacing) * 15); --text-display-lg--letter-spacing: -0.96px; --text-display-xl: calc(var(--spacing) * 15); --text-display-xl--line-height: calc(var(--spacing) * 18); --text-display-xl--letter-spacing: -1.2px; --text-display-2xl: calc(var(--spacing) * 18); --text-display-2xl--line-height: calc(var(--spacing) * 22.5); --text-display-2xl--letter-spacing: -1.44px; /* MAX WIDTH */ --max-width-container: 1280px; /* BREAKPOINTS */ --breakpoint-xxs: 320px; /* This must match the breakpoint in Sonner: https://github.com/emilkowalski/sonner/blob/main/src/styles.css */ --breakpoint-xs: 600px; /* RADIUS */ --radius-none: 0px; --radius-xs: 0.125rem; --radius-sm: 0.25rem; --radius-DEFAULT: 0.25rem; --radius-md: 0.375rem; --radius-lg: 0.5rem; --radius-xl: 0.75rem; --radius-2xl: 1rem; --radius-3xl: 1.5rem; --radius-full: 9999px; /* SHADOW */ --shadow-xs: 0px 1px 2px rgba(0, 0, 0, 0.05); --shadow-sm: 0px 1px 3px rgba(0, 0, 0, 0.1), 0px 1px 2px -1px rgba(0, 0, 0, 0.1); --shadow-md: 0px 4px 6px -1px rgba(0, 0, 0, 0.1), 0px 2px 4px -2px rgba(0, 0, 0, 0.06); --shadow-lg: 0px 12px 16px -4px rgba(0, 0, 0, 0.08), 0px 4px 6px -2px rgba(0, 0, 0, 0.03), 0px 2px 2px -1px rgba(0, 0, 0, 0.04); --shadow-xl: 0px 20px 24px -4px rgba(0, 0, 0, 0.08), 0px 8px 8px -4px rgba(0, 0, 0, 0.03), 0px 3px 3px -1.5px rgba(0, 0, 0, 0.04); --shadow-2xl: 0px 24px 48px -12px rgba(0, 0, 0, 0.18), 0px 4px 4px -2px rgba(0, 0, 0, 0.04); --shadow-3xl: 0px 32px 64px -12px rgba(0, 0, 0, 0.14), 0px 5px 5px -2.5px rgba(0, 0, 0, 0.04); --shadow-skeuomorphic: 0px 0px 0px 1px rgba(0, 0, 0, 0.18) inset, 0px -2px 0px 0px rgba(0, 0, 0, 0.05) inset; --shadow-xs-skeuomorphic: var(--shadow-skeuomorphic), var(--shadow-xs); --shadow-modern-mockup-inner-lg: 0px 0px 3.765px 1.255px rgba(10, 13, 18, 0.08) inset, 0px 0px 2.51px 1.255px rgba(10, 13, 18, 0.03) inset; --shadow-modern-mockup-inner-md: 0px 0px 1.692px 0.564px rgba(10, 13, 18, 0.08) inset, 0px 0px 1.128px 0.564px rgba(10, 13, 18, 0.03) inset; --shadow-modern-mockup-inner-sm: 0px 0px 4.48px 1.493px rgba(10, 13, 18, 0.08) inset, 0px 0px 2.987px 1.493px rgba(10, 13, 18, 0.03) inset; --shadow-modern-mockup-outer-lg: 0px 7.529px 10.039px -2.51px rgba(10, 13, 18, 0.08), 0px 2.51px 3.765px -1.255px rgba(10, 13, 18, 0.03), 0px 1.255px 1.255px -0.627px rgba(10, 13, 18, 0.04); --shadow-modern-mockup-outer-md: 0px 3.385px 4.513px -1.128px rgba(10, 13, 18, 0.08), 0px 1.128px 1.692px -0.564px rgba(10, 13, 18, 0.03), 0px 0.564px 0.564px -0.282px rgba(10, 13, 18, 0.04); --drop-shadow-iphone-mockup: 20px 12px 18px rgba(16, 24, 40, 0.2); /* ANIMATIONS */ --animate-marquee: marquee 60s linear infinite; --animate-caret-blink: caret-blink 1s infinite; @keyframes marquee { 0% { transform: translateX(0); } 100% { transform: translateX(-100%); } } @keyframes caret-blink { 0%, 50% { opacity: 1; } 51%, 100% { opacity: 0; } } /* BASE COLOR */ --color-transparent: rgb(0 0 0 / 0); --color-white: rgb(255 255 255); --color-black: rgb(0 0 0); /* These will be inverted in dark mode. */ --color-alpha-white: rgb(255 255 255); --color-alpha-black: rgb(0 0 0); --color-brand-50: rgb(249 245 255); --color-brand-100: rgb(244 235 255); --color-brand-200: rgb(233 215 254); --color-brand-300: rgb(214 187 251); --color-brand-400: rgb(182 146 246); --color-brand-500: rgb(158 119 237); --color-brand-600: rgb(127 86 217); --color-brand-700: rgb(105 65 198); --color-brand-800: rgb(83 56 158); --color-brand-900: rgb(66 48 125); --color-brand-950: rgb(44 28 95); /* LIGHT MODE VARIABLES */ --color-alpha-white: rgb(255 255 255); --color-alpha-black: rgb(0 0 0); /* UTILITY COLORS */ --color-utility-blue-50: var(--color-blue-50); --color-utility-blue-100: var(--color-blue-100); --color-utility-blue-200: var(--color-blue-200); --color-utility-blue-300: var(--color-blue-300); --color-utility-blue-400: var(--color-blue-400); --color-utility-blue-500: var(--color-blue-500); --color-utility-blue-600: var(--color-blue-600); --color-utility-blue-700: var(--color-blue-700); --color-utility-brand-50: var(--color-brand-50); --color-utility-brand-50_alt: var(--color-brand-50); --color-utility-brand-100: var(--color-brand-100); --color-utility-brand-100_alt: var(--color-brand-100); --color-utility-brand-200: var(--color-brand-200); --color-utility-brand-200_alt: var(--color-brand-200); --color-utility-brand-300: var(--color-brand-300); --color-utility-brand-300_alt: var(--color-brand-300); --color-utility-brand-400: var(--color-brand-400); --color-utility-brand-400_alt: var(--color-brand-400); --color-utility-brand-500: var(--color-brand-500); --color-utility-brand-500_alt: var(--color-brand-500); --color-utility-brand-600: var(--color-brand-600); --color-utility-brand-600_alt: var(--color-brand-600); --color-utility-brand-700: var(--color-brand-700); --color-utility-brand-700_alt: var(--color-brand-700); --color-utility-brand-800: var(--color-brand-800); --color-utility-brand-800_alt: var(--color-brand-800); --color-utility-brand-900: var(--color-brand-900); --color-utility-brand-900_alt: var(--color-brand-900); --color-utility-neutral-50: var(--color-neutral-50); --color-utility-neutral-100: var(--color-neutral-100); --color-utility-neutral-200: var(--color-neutral-200); --color-utility-neutral-300: var(--color-neutral-300); --color-utility-neutral-400: var(--color-neutral-400); --color-utility-neutral-500: var(--color-neutral-500); --color-utility-neutral-600: var(--color-neutral-600); --color-utility-neutral-700: var(--color-neutral-700); --color-utility-neutral-800: var(--color-neutral-800); --color-utility-neutral-900: var(--color-neutral-900); --color-utility-red-50: var(--color-red-50); --color-utility-red-100: var(--color-red-100); --color-utility-red-200: var(--color-red-200); --color-utility-red-300: var(--color-red-300); --color-utility-red-400: var(--color-red-400); --color-utility-red-500: var(--color-red-500); --color-utility-red-600: var(--color-red-600); --color-utility-red-700: var(--color-red-700); --color-utility-yellow-50: var(--color-yellow-50); --color-utility-yellow-100: var(--color-yellow-100); --color-utility-yellow-200: var(--color-yellow-200); --color-utility-yellow-300: var(--color-yellow-300); --color-utility-yellow-400: var(--color-yellow-400); --color-utility-yellow-500: var(--color-yellow-500); --color-utility-yellow-600: var(--color-yellow-600); --color-utility-yellow-700: var(--color-yellow-700); --color-utility-green-50: var(--color-green-50); --color-utility-green-100: var(--color-green-100); --color-utility-green-200: var(--color-green-200); --color-utility-green-300: var(--color-green-300); --color-utility-green-400: var(--color-green-400); --color-utility-green-500: var(--color-green-500); --color-utility-green-600: var(--color-green-600); --color-utility-green-700: var(--color-green-700); --color-utility-orange-50: var(--color-orange-50); --color-utility-orange-100: var(--color-orange-100); --color-utility-orange-200: var(--color-orange-200); --color-utility-orange-300: var(--color-orange-300); --color-utility-orange-400: var(--color-orange-400); --color-utility-orange-500: var(--color-orange-500); --color-utility-orange-600: var(--color-orange-600); --color-utility-orange-700: var(--color-orange-700); --color-utility-indigo-50: var(--color-indigo-50); --color-utility-indigo-100: var(--color-indigo-100); --color-utility-indigo-200: var(--color-indigo-200); --color-utility-indigo-300: var(--color-indigo-300); --color-utility-indigo-400: var(--color-indigo-400); --color-utility-indigo-500: var(--color-indigo-500); --color-utility-indigo-600: var(--color-indigo-600); --color-utility-indigo-700: var(--color-indigo-700); --color-utility-fuchsia-50: var(--color-fuchsia-50); --color-utility-fuchsia-100: var(--color-fuchsia-100); --color-utility-fuchsia-200: var(--color-fuchsia-200); --color-utility-fuchsia-300: var(--color-fuchsia-300); --color-utility-fuchsia-400: var(--color-fuchsia-400); --color-utility-fuchsia-500: var(--color-fuchsia-500); --color-utility-fuchsia-600: var(--color-fuchsia-600); --color-utility-fuchsia-700: var(--color-fuchsia-700); --color-utility-pink-50: var(--color-pink-50); --color-utility-pink-100: var(--color-pink-100); --color-utility-pink-200: var(--color-pink-200); --color-utility-pink-300: var(--color-pink-300); --color-utility-pink-400: var(--color-pink-400); --color-utility-pink-500: var(--color-pink-500); --color-utility-pink-600: var(--color-pink-600); --color-utility-pink-700: var(--color-pink-700); --color-utility-purple-50: var(--color-purple-50); --color-utility-purple-100: var(--color-purple-100); --color-utility-purple-200: var(--color-purple-200); --color-utility-purple-300: var(--color-purple-300); --color-utility-purple-400: var(--color-purple-400); --color-utility-purple-500: var(--color-purple-500); --color-utility-purple-600: var(--color-purple-600); --color-utility-purple-700: var(--color-purple-700); --color-utility-sky-50: var(--color-sky-50); --color-utility-sky-100: var(--color-sky-100); --color-utility-sky-200: var(--color-sky-200); --color-utility-sky-300: var(--color-sky-300); --color-utility-sky-400: var(--color-sky-400); --color-utility-sky-500: var(--color-sky-500); --color-utility-sky-600: var(--color-sky-600); --color-utility-sky-700: var(--color-sky-700); --color-utility-slate-50: var(--color-slate-50); --color-utility-slate-100: var(--color-slate-100); --color-utility-slate-200: var(--color-slate-200); --color-utility-slate-300: var(--color-slate-300); --color-utility-slate-400: var(--color-slate-400); --color-utility-slate-500: var(--color-slate-500); --color-utility-slate-600: var(--color-slate-600); --color-utility-slate-700: var(--color-slate-700); --color-utility-emerald-50: var(--color-emerald-50); --color-utility-emerald-100: var(--color-emerald-100); --color-utility-emerald-200: var(--color-emerald-200); --color-utility-emerald-300: var(--color-emerald-300); --color-utility-emerald-400: var(--color-emerald-400); --color-utility-emerald-500: var(--color-emerald-500); --color-utility-emerald-600: var(--color-emerald-600); --color-utility-emerald-700: var(--color-emerald-700); --color-utility-amber-50: var(--color-amber-50); --color-utility-amber-100: var(--color-amber-100); --color-utility-amber-200: var(--color-amber-200); --color-utility-amber-300: var(--color-amber-300); --color-utility-amber-400: var(--color-amber-400); --color-utility-amber-500: var(--color-amber-500); --color-utility-amber-600: var(--color-amber-600); --color-utility-amber-700: var(--color-amber-700); /* TEXT COLORS */ --color-text-primary: var(--color-neutral-900); --color-text-tertiary: var(--color-neutral-600); --color-text-error-primary: var(--color-red-600); --color-text-warning-primary: var(--color-yellow-600); --color-text-success-primary: var(--color-green-600); --color-text-white: var(--color-white); --color-text-secondary: var(--color-neutral-700); --color-text-secondary_hover: var(--color-neutral-800); --color-text-tertiary_hover: var(--color-neutral-700); --color-text-brand-secondary: var(--color-brand-700); --color-text-placeholder: var(--color-neutral-500); --color-text-brand-tertiary: var(--color-brand-600); --color-text-editor-icon-fg: var(--color-neutral-400); --color-text-editor-icon-fg_active: var(--color-neutral-500); --color-text-quaternary: var(--color-neutral-500); --color-text-brand-primary: var(--color-brand-900); --color-text-primary_on-brand: var(--color-white); --color-text-secondary_on-brand: var(--color-brand-200); --color-text-tertiary_on-brand: var(--color-brand-200); --color-text-quaternary_on-brand: var(--color-brand-300); --color-text-brand-tertiary_alt: var(--color-brand-600); --color-text-error-primary_hover: var(--color-red-700); --color-text-brand-secondary_hover: var(--color-brand-800); /* BORDER COLORS */ --color-border-primary: var(--color-neutral-300); --color-border-secondary: var(--color-neutral-200); --color-border-secondary_alt: rgb(0 0 0 / 0.1); --color-border-tertiary: var(--color-neutral-100); --color-border-error: var(--color-red-500); --color-border-error_subtle: var(--color-red-300); --color-border-brand: var(--color-brand-500); --color-border-brand_alt: var(--color-brand-600); /* FOREGROUND COLORS */ --color-fg-secondary: var(--color-neutral-700); --color-fg-warning-primary: var(--color-yellow-600); --color-fg-success-primary: var(--color-green-600); --color-fg-white: var(--color-white); --color-fg-success-secondary: var(--color-green-500); --color-fg-secondary_hover: var(--color-neutral-800); --color-fg-primary: var(--color-neutral-900); --color-fg-brand-secondary: var(--color-brand-500); --color-fg-brand-primary: var(--color-brand-600); --color-fg-quaternary: var(--color-neutral-400); --color-fg-quaternary_hover: var(--color-neutral-500); --color-fg-error-primary: var(--color-red-600); --color-fg-warning-secondary: var(--color-yellow-500); --color-fg-error-secondary: var(--color-red-500); --color-fg-tertiary: var(--color-neutral-600); --color-fg-tertiary_hover: var(--color-neutral-700); --color-fg-brand-primary_alt: var(--color-fg-brand-primary); --color-fg-brand-secondary_alt: var(--color-fg-brand-secondary); --color-fg-brand-secondary_hover: var(--color-brand-600); /* BACKGROUND COLORS */ --color-bg-primary: var(--color-white); --color-bg-tertiary: var(--color-neutral-100); --color-bg-brand-primary: var(--color-brand-50); --color-bg-error-secondary: var(--color-red-100); --color-bg-warning-primary: var(--color-yellow-50); --color-bg-warning-secondary: var(--color-yellow-100); --color-bg-success-primary: var(--color-green-50); --color-bg-success-secondary: var(--color-green-100); --color-bg-brand-solid: var(--color-brand-600); --color-bg-secondary-solid: var(--color-neutral-600); --color-bg-error-solid: var(--color-red-600); --color-bg-warning-solid: var(--color-yellow-600); --color-bg-success-solid: var(--color-green-600); --color-bg-secondary_hover: var(--color-neutral-100); --color-bg-primary_hover: var(--color-neutral-50); --color-bg-active: var(--color-neutral-50); --color-bg-brand-solid_hover: var(--color-brand-700); --color-bg-error-primary: var(--color-red-50); --color-bg-brand-secondary: var(--color-brand-100); --color-bg-secondary: var(--color-neutral-50); --color-bg-quaternary: var(--color-neutral-200); --color-bg-primary_alt: var(--color-white); --color-bg-brand-primary_alt: var(--color-brand-50); --color-bg-secondary_alt: var(--color-neutral-50); --color-bg-overlay: var(--color-neutral-950); --color-bg-brand-section: var(--color-brand-800); --color-bg-brand-section_subtle: var(--color-brand-700); --color-bg-primary-solid: var(--color-neutral-950); --color-bg-error-solid_hover: var(--color-red-700); /* COMPONENT COLORS */ --color-app-store-badge-border: 166 166 166 1; --color-avatar-styles-bg-neutral: 224 224 224 1; --color-featured-icon-light-fg-brand: var(--color-brand-600); --color-featured-icon-light-fg-error: var(--color-red-600); --color-featured-icon-light-fg-gray: var(--color-neutral-500); --color-featured-icon-light-fg-success: var(--color-green-600); --color-featured-icon-light-fg-warning: var(--color-yellow-600); --color-focus-ring-error: var(--color-red-500); --color-focus-ring: var(--color-brand-500); --color-footer-button-fg: var(--color-brand-200); --color-footer-button-fg_hover: var(--color-white); --color-icon-fg-brand: var(--color-brand-600); --color-icon-fg-brand_on-brand: var(--color-brand-200); --color-screen-mockup-border: var(--color-neutral-900); --color-slider-handle-bg: var(--color-white); --color-slider-handle-border: var(--color-brand-600); --color-toggle-border: var(--color-neutral-300); --color-toggle-slim-border_pressed-hover: var(--color-bg-brand-solid_hover); --color-toggle-slim-border_pressed: var(--color-bg-brand-solid); --color-tooltip-supporting-text: var(--color-neutral-300); /* BACKGROUND PROPERTY COLORS */ --background-color-primary: var(--color-bg-primary); --background-color-tertiary: var(--color-bg-tertiary); --background-color-brand-primary: var(--color-bg-brand-primary); --background-color-error-secondary: var(--color-bg-error-secondary); --background-color-warning-primary: var(--color-bg-warning-primary); --background-color-warning-secondary: var(--color-bg-warning-secondary); --background-color-success-primary: var(--color-bg-success-primary); --background-color-success-secondary: var(--color-bg-success-secondary); --background-color-brand-solid: var(--color-bg-brand-solid); --background-color-secondary-solid: var(--color-bg-secondary-solid); --background-color-error-solid: var(--color-bg-error-solid); --background-color-warning-solid: var(--color-bg-warning-solid); --background-color-success-solid: var(--color-bg-success-solid); --background-color-secondary_hover: var(--color-bg-secondary_hover); --background-color-primary_hover: var(--color-bg-primary_hover); --background-color-active: var(--color-bg-active); --background-color-brand-solid_hover: var(--color-bg-brand-solid_hover); --background-color-error-primary: var(--color-bg-error-primary); --background-color-brand-secondary: var(--color-bg-brand-secondary); --background-color-secondary: var(--color-bg-secondary); --background-color-quaternary: var(--color-bg-quaternary); --background-color-primary_alt: var(--color-bg-primary_alt); --background-color-brand-primary_alt: var(--color-bg-brand-primary_alt); --background-color-secondary_alt: var(--color-bg-secondary_alt); --background-color-overlay: var(--color-bg-overlay); --background-color-brand-section: var(--color-bg-brand-section); --background-color-brand-section_subtle: var(--color-bg-brand-section_subtle); --background-color-primary-solid: var(--color-bg-primary-solid); --background-color-error-solid_hover: var(--color-bg-error-solid_hover); --background-color-border-brand: var(--color-border-brand); --background-color-border-brand_alt: var(--color-border-brand_alt); /* TEXT PROPERTY COLORS */ --color-text-white: var(--color-white); --text-color-primary: var(--color-text-primary); --text-color-secondary: var(--color-text-secondary); --text-color-secondary_hover: var(--color-text-secondary_hover); --text-color-tertiary: var(--color-text-tertiary); --text-color-tertiary_hover: var(--color-text-tertiary_hover); --text-color-error-primary: var(--color-text-error-primary); --text-color-warning-primary: var(--color-text-warning-primary); --text-color-success-primary: var(--color-text-success-primary); --text-color-brand-secondary: var(--color-text-brand-secondary); --text-color-placeholder: var(--color-text-placeholder); --text-color-brand-tertiary: var(--color-text-brand-tertiary); --text-color-editor-icon-fg: var(--color-text-editor-icon-fg); --text-color-editor-icon-fg_active: var(--color-text-editor-icon-fg_active); --text-color-quaternary: var(--color-text-quaternary); --text-color-brand-primary: var(--color-text-brand-primary); --text-color-primary_on-brand: var(--color-text-primary_on-brand); --text-color-secondary_on-brand: var(--color-text-secondary_on-brand); --text-color-tertiary_on-brand: var(--color-text-tertiary_on-brand); --text-color-quaternary_on-brand: var(--color-text-quaternary_on-brand); --text-color-brand-tertiary_alt: var(--color-text-brand-tertiary_alt); --text-color-error-primary_hover: var(--color-text-error-primary_hover); --text-color-brand-secondary_hover: var(--color-text-brand-secondary_hover); --text-color-tooltip-supporting-text: var(--color-tooltip-supporting-text); /* BORDER PROPERTY COLORS */ --border-color-primary: var(--color-border-primary); --border-color-secondary: var(--color-border-secondary); --border-color-secondary_alt: var(--color-border-secondary_alt); --border-color-tertiary: var(--color-border-tertiary); --border-color-error: var(--color-border-error); --border-color-error_subtle: var(--color-border-error_subtle); --border-color-brand: var(--color-border-brand); --border-color-brand_alt: var(--color-border-brand_alt); --border-color-brand-solid: var(--color-bg-brand-solid); --border-color-brand-solid_hover: var(--color-bg-brand-solid_hover); /* RING PROPERTY COLORS */ --ring-color-primary: var(--color-border-primary); --ring-color-secondary: var(--color-border-secondary); --ring-color-secondary_alt: var(--color-border-secondary_alt); --ring-color-tertiary: var(--color-border-tertiary); --ring-color-error: var(--color-border-error); --ring-color-error_subtle: var(--color-border-error_subtle); --ring-color-brand: var(--color-border-brand); --ring-color-brand_alt: var(--color-border-brand_alt); --ring-color-brand-solid: var(--color-bg-brand-solid); --ring-color-brand-solid_hover: var(--color-bg-brand-solid_hover); /* OUTLINE PROPERTY COLORS */ --outline-color-primary: var(--color-border-primary); --outline-color-secondary: var(--color-border-secondary); --outline-color-secondary_alt: var(--color-border-secondary_alt); --outline-color-tertiary: var(--color-border-tertiary); --outline-color-error: var(--color-border-error); --outline-color-error_subtle: var(--color-border-error_subtle); --outline-color-brand: var(--color-border-brand); --outline-color-brand_alt: var(--color-border-brand_alt); --outline-color-brand-solid: var(--color-bg-brand-solid); --outline-color-brand-solid_hover: var(--color-bg-brand-solid_hover); } @layer base { /* DARK MODE VARIABLES */ .dark-mode { --color-alpha-white: rgb(12 14 18); --color-alpha-black: rgb(255 255 255); /* UTILITY COLORS */ --color-utility-blue-50: var(--color-blue-950); --color-utility-blue-100: var(--color-blue-900); --color-utility-blue-200: var(--color-blue-800); --color-utility-blue-300: var(--color-blue-700); --color-utility-blue-400: var(--color-blue-600); --color-utility-blue-500: var(--color-blue-500); --color-utility-blue-600: var(--color-blue-400); --color-utility-blue-700: var(--color-blue-300); --color-utility-brand-50: var(--color-brand-950); --color-utility-brand-50_alt: var(--color-utility-neutral-50); --color-utility-brand-100: var(--color-brand-900); --color-utility-brand-100_alt: var(--color-utility-neutral-100); --color-utility-brand-200: var(--color-brand-800); --color-utility-brand-200_alt: var(--color-utility-neutral-200); --color-utility-brand-300: var(--color-brand-700); --color-utility-brand-300_alt: var(--color-utility-neutral-300); --color-utility-brand-400: var(--color-brand-600); --color-utility-brand-400_alt: var(--color-utility-neutral-400); --color-utility-brand-500: var(--color-brand-500); --color-utility-brand-500_alt: var(--color-utility-neutral-500); --color-utility-brand-600: var(--color-brand-400); --color-utility-brand-600_alt: var(--color-utility-neutral-600); --color-utility-brand-700: var(--color-brand-300); --color-utility-brand-700_alt: var(--color-utility-neutral-700); --color-utility-brand-800: var(--color-brand-200); --color-utility-brand-800_alt: var(--color-utility-neutral-800); --color-utility-brand-900: var(--color-brand-100); --color-utility-brand-900_alt: var(--color-utility-neutral-900); --color-utility-neutral-50: var(--color-neutral-900); --color-utility-neutral-100: var(--color-neutral-800); --color-utility-neutral-200: var(--color-neutral-700); --color-utility-neutral-300: var(--color-neutral-700); --color-utility-neutral-400: var(--color-neutral-600); --color-utility-neutral-500: var(--color-neutral-500); --color-utility-neutral-600: var(--color-neutral-400); --color-utility-neutral-700: var(--color-neutral-300); --color-utility-neutral-800: var(--color-neutral-200); --color-utility-neutral-900: var(--color-neutral-100); --color-utility-red-50: var(--color-red-950); --color-utility-red-100: var(--color-red-900); --color-utility-red-200: var(--color-red-800); --color-utility-red-300: var(--color-red-700); --color-utility-red-400: var(--color-red-600); --color-utility-red-500: var(--color-red-500); --color-utility-red-600: var(--color-red-400); --color-utility-red-700: var(--color-red-300); --color-utility-yellow-50: var(--color-yellow-950); --color-utility-yellow-100: var(--color-yellow-900); --color-utility-yellow-200: var(--color-yellow-800); --color-utility-yellow-300: var(--color-yellow-700); --color-utility-yellow-400: var(--color-yellow-600); --color-utility-yellow-500: var(--color-yellow-500); --color-utility-yellow-600: var(--color-yellow-400); --color-utility-yellow-700: var(--color-yellow-300); --color-utility-green-50: var(--color-green-950); --color-utility-green-100: var(--color-green-900); --color-utility-green-200: var(--color-green-800); --color-utility-green-300: var(--color-green-700); --color-utility-green-400: var(--color-green-600); --color-utility-green-500: var(--color-green-500); --color-utility-green-600: var(--color-green-400); --color-utility-green-700: var(--color-green-300); --color-utility-orange-50: var(--color-orange-950); --color-utility-orange-100: var(--color-orange-900); --color-utility-orange-200: var(--color-orange-800); --color-utility-orange-300: var(--color-orange-700); --color-utility-orange-400: var(--color-orange-600); --color-utility-orange-500: var(--color-orange-500); --color-utility-orange-600: var(--color-orange-400); --color-utility-orange-700: var(--color-orange-300); --color-utility-indigo-50: var(--color-indigo-950); --color-utility-indigo-100: var(--color-indigo-900); --color-utility-indigo-200: var(--color-indigo-800); --color-utility-indigo-300: var(--color-indigo-700); --color-utility-indigo-400: var(--color-indigo-600); --color-utility-indigo-500: var(--color-indigo-500); --color-utility-indigo-600: var(--color-indigo-400); --color-utility-indigo-700: var(--color-indigo-300); --color-utility-fuchsia-50: var(--color-fuchsia-950); --color-utility-fuchsia-100: var(--color-fuchsia-900); --color-utility-fuchsia-200: var(--color-fuchsia-800); --color-utility-fuchsia-300: var(--color-fuchsia-700); --color-utility-fuchsia-400: var(--color-fuchsia-600); --color-utility-fuchsia-500: var(--color-fuchsia-500); --color-utility-fuchsia-600: var(--color-fuchsia-400); --color-utility-fuchsia-700: var(--color-fuchsia-300); --color-utility-pink-50: var(--color-pink-950); --color-utility-pink-100: var(--color-pink-900); --color-utility-pink-200: var(--color-pink-800); --color-utility-pink-300: var(--color-pink-700); --color-utility-pink-400: var(--color-pink-600); --color-utility-pink-500: var(--color-pink-500); --color-utility-pink-600: var(--color-pink-400); --color-utility-pink-700: var(--color-pink-300); --color-utility-purple-50: var(--color-purple-950); --color-utility-purple-100: var(--color-purple-900); --color-utility-purple-200: var(--color-purple-800); --color-utility-purple-300: var(--color-purple-700); --color-utility-purple-400: var(--color-purple-600); --color-utility-purple-500: var(--color-purple-500); --color-utility-purple-600: var(--color-purple-400); --color-utility-purple-700: var(--color-purple-300); --color-utility-sky-50: var(--color-sky-950); --color-utility-sky-100: var(--color-sky-900); --color-utility-sky-200: var(--color-sky-800); --color-utility-sky-300: var(--color-sky-700); --color-utility-sky-400: var(--color-sky-600); --color-utility-sky-500: var(--color-sky-500); --color-utility-sky-600: var(--color-sky-400); --color-utility-sky-700: var(--color-sky-300); --color-utility-slate-50: var(--color-slate-950); --color-utility-slate-100: var(--color-slate-900); --color-utility-slate-200: var(--color-slate-800); --color-utility-slate-300: var(--color-slate-700); --color-utility-slate-400: var(--color-slate-600); --color-utility-slate-500: var(--color-slate-500); --color-utility-slate-600: var(--color-slate-400); --color-utility-slate-700: var(--color-slate-300); --color-utility-emerald-50: var(--color-emerald-950); --color-utility-emerald-100: var(--color-emerald-900); --color-utility-emerald-200: var(--color-emerald-800); --color-utility-emerald-300: var(--color-emerald-700); --color-utility-emerald-400: var(--color-emerald-600); --color-utility-emerald-500: var(--color-emerald-500); --color-utility-emerald-600: var(--color-emerald-400); --color-utility-emerald-700: var(--color-emerald-300); --color-utility-amber-50: var(--color-amber-950); --color-utility-amber-100: var(--color-amber-900); --color-utility-amber-200: var(--color-amber-800); --color-utility-amber-300: var(--color-amber-700); --color-utility-amber-400: var(--color-amber-600); --color-utility-amber-500: var(--color-amber-500); --color-utility-amber-600: var(--color-amber-400); --color-utility-amber-700: var(--color-amber-300); --color-text-primary: var(--color-neutral-50); --color-text-tertiary: var(--color-neutral-400); --color-text-error-primary: var(--color-red-400); --color-text-warning-primary: var(--color-yellow-400); --color-text-success-primary: var(--color-green-400); --color-text-white: var(--color-white); --color-text-secondary: var(--color-neutral-300); --color-text-secondary_hover: var(--color-neutral-200); --color-text-tertiary_hover: var(--color-neutral-300); --color-text-brand-secondary: var(--color-neutral-300); --color-text-placeholder: var(--color-neutral-500); --color-text-brand-tertiary: var(--color-neutral-400); --color-text-editor-icon-fg: var(--color-neutral-400); --color-text-editor-icon-fg_active: var(--color-white); --color-text-quaternary: var(--color-neutral-400); --color-text-brand-primary: var(--color-neutral-50); --color-text-primary_on-brand: var(--color-neutral-50); --color-text-secondary_on-brand: var(--color-neutral-300); --color-text-tertiary_on-brand: var(--color-neutral-400); --color-text-quaternary_on-brand: var(--color-neutral-400); --color-text-brand-tertiary_alt: var(--color-neutral-50); --color-text-error-primary_hover: var(--color-red-300); --color-text-brand-secondary_hover: var(--color-neutral-200); --color-border-secondary: var(--color-neutral-800); --color-border-error_subtle: var(--color-red-500); --color-border-primary: var(--color-neutral-700); --color-border-brand: var(--color-brand-400); --color-border-error: var(--color-red-400); --color-border-tertiary: var(--color-neutral-800); --color-border-brand_alt: var(--color-neutral-700); --color-border-secondary_alt: var(--color-neutral-800); --color-fg-secondary: var(--color-neutral-300); --color-fg-warning-primary: var(--color-yellow-500); --color-fg-success-primary: var(--color-green-500); --color-fg-white: var(--color-white); --color-fg-success-secondary: var(--color-green-400); --color-fg-secondary_hover: var(--color-neutral-200); --color-fg-primary: var(--color-white); --color-fg-brand-secondary: var(--color-brand-500); --color-fg-brand-primary: var(--color-brand-500); --color-fg-quaternary: var(--color-neutral-600); --color-fg-quaternary_hover: var(--color-neutral-500); --color-fg-error-primary: var(--color-red-500); --color-fg-warning-secondary: var(--color-yellow-400); --color-fg-error-secondary: var(--color-red-400); --color-fg-tertiary: var(--color-neutral-400); --color-fg-tertiary_hover: var(--color-neutral-300); --color-fg-brand-primary_alt: var(--color-neutral-300); --color-fg-brand-secondary_alt: var(--color-neutral-600); --color-fg-brand-secondary_hover: var(--color-neutral-500); --color-bg-primary: var(--color-neutral-950); --color-bg-tertiary: var(--color-neutral-800); --color-bg-brand-primary: var(--color-brand-500); --color-bg-error-secondary: var(--color-red-600); --color-bg-warning-primary: var(--color-yellow-950); --color-bg-warning-secondary: var(--color-yellow-600); --color-bg-success-primary: var(--color-green-950); --color-bg-success-secondary: var(--color-green-600); --color-bg-brand-solid: var(--color-brand-600); --color-bg-secondary-solid: var(--color-neutral-600); --color-bg-error-solid: var(--color-red-600); --color-bg-warning-solid: var(--color-yellow-600); --color-bg-success-solid: var(--color-green-600); --color-bg-secondary_hover: var(--color-neutral-800); --color-bg-primary_hover: var(--color-neutral-900); --color-bg-active: var(--color-neutral-800); --color-bg-brand-solid_hover: var(--color-brand-500); --color-bg-error-primary: var(--color-red-950); --color-bg-brand-secondary: var(--color-brand-600); --color-bg-secondary: var(--color-neutral-900); --color-bg-quaternary: var(--color-neutral-700); --color-bg-primary_alt: var(--color-bg-secondary); --color-bg-brand-primary_alt: var(--color-bg-secondary); --color-bg-secondary_alt: var(--color-bg-primary); --color-bg-overlay: var(--color-neutral-800); --color-bg-brand-section: var(--color-bg-secondary); --color-bg-brand-section_subtle: var(--color-bg-primary); --color-bg-primary-solid: var(--color-bg-secondary); --color-bg-error-solid_hover: var(--color-red-500); --color-app-store-badge-border: var(--color-white); --color-avatar-styles-bg-neutral: 224 224 224 1; --color-featured-icon-light-fg-brand: var(--color-brand-200); --color-featured-icon-light-fg-error: var(--color-red-200); --color-featured-icon-light-fg-gray: var(--color-neutral-200); --color-featured-icon-light-fg-success: var(--color-green-200); --color-featured-icon-light-fg-warning: var(--color-yellow-200); --color-focus-ring-error: var(--color-red-500); --color-focus-ring: var(--color-brand-500); --color-footer-button-fg: var(--color-neutral-300); --color-footer-button-fg_hover: var(--color-neutral-100); --color-icon-fg-brand: var(--color-neutral-400); --color-icon-fg-brand_on-brand: var(--color-neutral-400); --color-screen-mockup-border: var(--color-neutral-700); --color-slider-handle-bg: var(--color-fg-brand-primary); --color-slider-handle-border: var(--color-bg-primary); --color-toggle-border: var(--color-transparent); --color-toggle-slim-border_pressed-hover: var(--color-transparent); --color-toggle-slim-border_pressed: var(--color-transparent); --color-tooltip-supporting-text: var(--color-neutral-300); /* BACKGROUND PROPERTY COLORS */ --background-color-primary: var(--color-bg-primary); --background-color-tertiary: var(--color-bg-tertiary); --background-color-brand-primary: var(--color-bg-brand-primary); --background-color-error-secondary: var(--color-bg-error-secondary); --background-color-warning-primary: var(--color-bg-warning-primary); --background-color-warning-secondary: var(--color-bg-warning-secondary); --background-color-success-primary: var(--color-bg-success-primary); --background-color-success-secondary: var(--color-bg-success-secondary); --background-color-brand-solid: var(--color-bg-brand-solid); --background-color-secondary-solid: var(--color-bg-secondary-solid); --background-color-error-solid: var(--color-bg-error-solid); --background-color-warning-solid: var(--color-bg-warning-solid); --background-color-success-solid: var(--color-bg-success-solid); --background-color-secondary_hover: var(--color-bg-secondary_hover); --background-color-primary_hover: var(--color-bg-primary_hover); --background-color-active: var(--color-bg-active); --background-color-brand-solid_hover: var(--color-bg-brand-solid_hover); --background-color-error-primary: var(--color-bg-error-primary); --background-color-brand-secondary: var(--color-bg-brand-secondary); --background-color-secondary: var(--color-bg-secondary); --background-color-quaternary: var(--color-bg-quaternary); --background-color-primary_alt: var(--color-bg-primary_alt); --background-color-brand-primary_alt: var(--color-bg-brand-primary_alt); --background-color-secondary_alt: var(--color-bg-secondary_alt); --background-color-overlay: var(--color-bg-overlay); --background-color-brand-section: var(--color-bg-brand-section); --background-color-brand-section_subtle: var(--color-bg-brand-section_subtle); --background-color-primary-solid: var(--color-bg-primary-solid); --background-color-error-solid_hover: var(--color-bg-error-solid_hover); --background-color-border-brand: var(--color-border-brand); --background-color-border-tertiary: var(--color-border-tertiary); --background-color-border-brand_alt: var(--color-border-brand_alt); /* TEXT PROPERTY COLORS */ --text-color-primary: var(--color-text-primary); --text-color-tertiary: var(--color-text-tertiary); --text-color-error-primary: var(--color-text-error-primary); --text-color-warning-primary: var(--color-text-warning-primary); --text-color-success-primary: var(--color-text-success-primary); --text-color-white: var(--color-text-white); --text-color-secondary: var(--color-text-secondary); --text-color-secondary_hover: var(--color-text-secondary_hover); --text-color-tertiary_hover: var(--color-text-tertiary_hover); --text-color-brand-secondary: var(--color-text-brand-secondary); --text-color-placeholder: var(--color-text-placeholder); --text-color-brand-tertiary: var(--color-text-brand-tertiary); --text-color-editor-icon-fg: var(--color-text-editor-icon-fg); --text-color-editor-icon-fg_active: var(--color-text-editor-icon-fg_active); --text-color-quaternary: var(--color-text-quaternary); --text-color-brand-primary: var(--color-text-brand-primary); --text-color-primary_on-brand: var(--color-text-primary_on-brand); --text-color-secondary_on-brand: var(--color-text-secondary_on-brand); --text-color-tertiary_on-brand: var(--color-text-tertiary_on-brand); --text-color-quaternary_on-brand: var(--color-text-quaternary_on-brand); --text-color-brand-tertiary_alt: var(--color-text-brand-tertiary_alt); --text-color-error-primary_hover: var(--color-text-error-primary_hover); --text-color-brand-secondary_hover: var(--color-text-brand-secondary_hover); --text-color-tooltip-supporting-text: var(--color-tooltip-supporting-text); /* BORDER PROPERTY COLORS */ --border-color-primary: var(--color-border-primary); --border-color-secondary: var(--color-border-secondary); --border-color-secondary_alt: var(--color-border-secondary_alt); --border-color-tertiary: var(--color-border-tertiary); --border-color-error: var(--color-border-error); --border-color-error_subtle: var(--color-border-error_subtle); --border-color-brand: var(--color-border-brand); --border-color-brand_alt: var(--color-border-brand_alt); --border-color-brand-solid: var(--color-bg-brand-solid); --border-color-brand-solid_hover: var(--color-bg-brand-solid_hover); /* RING PROPERTY COLORS */ --ring-color-primary: var(--color-border-primary); --ring-color-secondary: var(--color-border-secondary); --ring-color-secondary_alt: var(--color-border-secondary_alt); --ring-color-tertiary: var(--color-border-tertiary); --ring-color-error: var(--color-border-error); --ring-color-error_subtle: var(--color-border-error_subtle); --ring-color-brand: var(--color-border-brand); --ring-color-brand-solid: var(--color-bg-brand-solid); --ring-color-brand-solid_hover: var(--color-bg-brand-solid_hover); --ring-color-brand_alt: var(--color-border-brand_alt); /* OUTLINE PROPERTY COLORS */ --outline-color-primary: var(--color-border-primary); --outline-color-secondary: var(--color-border-secondary); --outline-color-secondary_alt: var(--color-border-secondary_alt); --outline-color-tertiary: var(--color-border-tertiary); --outline-color-error: var(--color-border-error); --outline-color-error_subtle: var(--color-border-error_subtle); --outline-color-brand: var(--color-border-brand); --outline-color-brand-solid: var(--color-bg-brand-solid); --outline-color-brand-solid_hover: var(--color-bg-brand-solid_hover); --outline-color-brand_alt: var(--color-border-brand_alt); } } ``` {/* @embed:end */} Now, create a `globals.css` file next to it that imports the `theme.css` and necessary plugins. It replaces the template's `src/styles.css`, which you can delete: ```javascript @import "tailwindcss"; @import "./theme.css"; @plugin "tailwindcss-animate"; @plugin "tailwindcss-react-aria-components"; @custom-variant dark (&:where(.dark-mode, .dark-mode *)); @custom-variant label (& [data-label]); @custom-variant focus-input-within (&:has(input:focus)); @utility scrollbar-hide { /* For Webkit-based browsers (Chrome, Safari and Opera) */ &::-webkit-scrollbar { display: none; -webkit-appearance: none; } /* For IE, Edge and Firefox */ -ms-overflow-style: none; /* IE and Edge */ scrollbar-width: none; /* Firefox */ } @utility transition-inherit-all { transition-property: inherit; transition-duration: inherit; transition-timing-function: inherit; } html, body { font-family: var(--font-body); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; -webkit-font-variant-ligatures: contextual; font-variant-ligatures: contextual; -webkit-font-kerning: normal; font-kerning: normal; } /* Hide the default expand arrow on Safari. */ details summary::-webkit-details-marker { display: none; } /* Hide default arrows from number inputs. */ /* Chrome, Safari, Edge, Opera */ input::-webkit-outer-spin-button, input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; } /* Firefox */ input[type="number"] { -moz-appearance: textfield; } /* Hide the default clear button (X) from search inputs. */ input[type="search"]::-webkit-search-cancel-button { -webkit-appearance: none; } /* Hide the default outline on chart elements. */ .recharts-surface, .recharts-surface g, .recharts-surface path { outline: none; } ``` In order to make the development experience slightly better we have added a few utility functions which we'll use throughout our project. Create these files under the `src/utils` and `src/hooks` directories. ```ts import { extendTailwindMerge } from "tailwind-merge"; const twMerge = extendTailwindMerge({ extend: { theme: { text: ["display-xs", "display-sm", "display-md", "display-lg", "display-xl", "display-2xl"], }, }, }); /** * This function is a wrapper around the twMerge function. * It is used to merge the classes inside style objects. */ export const cx = twMerge; /** * This function does nothing besides helping us to be able to * sort the classes inside style objects which is not supported * by the Tailwind IntelliSense by default. */ export function sortCx>>>(classes: T): T { return classes; } ``` ```ts /* We cannot use type `unknown` instead of `any` here because it will break the type assertion `isReactComponent` function is providing. */ /* eslint-disable @typescript-eslint/no-explicit-any */ import type React from "react"; type ReactComponent = React.FC | React.ComponentClass; /** * Checks if a given value is a function component. */ export const isFunctionComponent = (component: any): component is React.FC => { return typeof component === "function"; }; /** * Checks if a given value is a class component. */ export const isClassComponent = (component: any): component is React.ComponentClass => { return typeof component === "function" && component.prototype && (!!component.prototype.isReactComponent || !!component.prototype.render); }; /** * Checks if a given value is a forward ref component. */ export const isForwardRefComponent = (component: any): component is React.ForwardRefExoticComponent => { return typeof component === "object" && component !== null && component.$$typeof.toString() === "Symbol(react.forward_ref)"; }; /** * Checks if a given value is a valid React component. */ export const isReactComponent = (component: any): component is ReactComponent => { return isFunctionComponent(component) || isForwardRefComponent(component) || isClassComponent(component); }; ``` ```ts import { useEffect, useState } from "react"; const screens = { sm: "640px", md: "768px", lg: "1024px", xl: "1280px", "2xl": "1536px", }; /** * Checks whether a particular Tailwind CSS viewport size applies. * * @param size The size to check, which must either be included in Tailwind CSS's * list of default screen sizes, or added to the Tailwind CSS config file. * * @returns A boolean indicating whether the viewport size applies. */ export const useBreakpoint = (size: "sm" | "md" | "lg" | "xl" | "2xl") => { const [matches, setMatches] = useState(typeof window !== "undefined" ? window.matchMedia(`(min-width: ${screens[size]})`).matches : true); useEffect(() => { const breakpoint = window.matchMedia(`(min-width: ${screens[size]})`); setMatches(breakpoint.matches); const handleChange = (value: MediaQueryListEvent) => setMatches(value.matches); breakpoint.addEventListener("change", handleChange); return () => breakpoint.removeEventListener("change", handleChange); }, [size]); return matches; }; ``` ```ts import { useCallback, useState } from "react"; const DEFAULT_TIMEOUT = 2000; type UseClipboardReturnType = { /** * The state indicating whether the text has been copied. * If a string is provided, it will be used as the identifier for the copied state. */ copied: string | boolean; /** * Function to copy text to the clipboard using the modern clipboard API. * Falls back to the fallback function if the modern API fails. * * @param {string} text - The text to be copied. * @param {string} [id] - Optional identifier to set the copied state. * @returns {Promise} - A promise that resolves to an object containing: * - `success` (boolean): Whether the copy operation was successful. * - `error` (Error | undefined): The error object if the copy operation failed. */ copy: (text: string, id?: string) => Promise<{ success: boolean; error?: Error }>; }; /** * Custom hook to copy text to the clipboard. * * @returns {UseClipboardReturnType} - An object containing the copied state and the copy function. */ export const useClipboard = (): UseClipboardReturnType => { const [copied, setCopied] = useState(false); // Fallback function for older browsers const fallback = (text: string, id?: string) => { try { // Textarea to copy the text to the clipboard const textArea = document.createElement("textarea"); textArea.value = text; textArea.style.position = "absolute"; textArea.style.left = "-99999px"; document.body.appendChild(textArea); textArea.select(); const success = document.execCommand("copy"); textArea.remove(); setCopied(id || true); setTimeout(() => setCopied(false), DEFAULT_TIMEOUT); return success ? { success: true } : { success: false, error: new Error("execCommand returned false") }; } catch (err) { return { success: false, error: err instanceof Error ? err : new Error("Fallback copy failed"), }; } }; const copy = useCallback(async (text: string, id?: string) => { if (navigator.clipboard && window.isSecureContext) { try { await navigator.clipboard.writeText(text); setCopied(id || true); setTimeout(() => setCopied(false), DEFAULT_TIMEOUT); return { success: true }; } catch (err) { // If modern method fails, try fallback return fallback(text, id); } } return fallback(text); }, []); return { copied, copy }; }; ``` For client-side routing integration with React Aria components, create a `RouteProvider` component in `src/providers/router-provider.tsx` or in your desired location: ```tsx import type { PropsWithChildren } from "react"; import { useRouter } from "@tanstack/react-router"; import { RouterProvider } from "react-aria-components"; export const RouteProvider = ({ children }: PropsWithChildren) => { const router = useRouter(); return ( router.navigate({ to: href })} useHref={(href) => router.buildLocation({ to: href }).href}> {children} ); }; ``` `navigate` hands React Aria links, such as a `Button` with an `href`, to TanStack Router, so they navigate client-side instead of reloading the page, and `useHref` asks the router to build the `href` each link renders. For dark mode support, add a `ThemeProvider` component in `src/providers/theme-provider.tsx` or in your desired location: ```tsx import type { ReactNode } from "react"; import { createContext, useContext, useEffect, useState } from "react"; type Theme = "light" | "dark" | "system"; interface ThemeContextType { theme: Theme; setTheme: (theme: Theme) => void; } const ThemeContext = createContext(undefined); export const useTheme = (): ThemeContextType => { const context = useContext(ThemeContext); if (context === undefined) { throw new Error("useTheme must be used within a ThemeProvider"); } return context; }; interface ThemeProviderProps { children: ReactNode; /** * The class to add to the root element when the theme is dark * @default "dark-mode" */ darkModeClass?: string; /** * The default theme to use if no theme is stored in localStorage * @default "system" */ defaultTheme?: Theme; /** * The key to use to store the theme in localStorage * @default "ui-theme" */ storageKey?: string; } export const ThemeProvider = ({ children, defaultTheme = "system", storageKey = "ui-theme", darkModeClass = "dark-mode" }: ThemeProviderProps) => { const [theme, setTheme] = useState(() => { if (typeof window !== "undefined") { const savedTheme = localStorage.getItem(storageKey) as Theme | null; return savedTheme || defaultTheme; } return defaultTheme; }); useEffect(() => { const applyTheme = () => { const root = window.document.documentElement; if (theme === "system") { const systemTheme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"; root.classList.toggle(darkModeClass, systemTheme === "dark"); localStorage.removeItem(storageKey); } else { root.classList.toggle(darkModeClass, theme === "dark"); localStorage.setItem(storageKey, theme); } }; applyTheme(); // Listen for system theme changes const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)"); const handleChange = () => { if (theme === "system") { applyTheme(); } }; mediaQuery.addEventListener("change", handleChange); return () => mediaQuery.removeEventListener("change", handleChange); }, [theme]); return {children}; }; ``` Update the root route in `src/routes/__root.tsx` to include the providers, the Inter font and the global styles: ```tsx import { HeadContent, Scripts, createRootRoute } from "@tanstack/react-router"; import { TanStackRouterDevtoolsPanel } from "@tanstack/react-router-devtools"; import { TanStackDevtools } from "@tanstack/react-devtools"; import { RouteProvider } from "@/providers/router-provider"; import { ThemeProvider } from "@/providers/theme-provider"; import appCss from "@/styles/globals.css?url"; // Applies the saved theme before the page paints, so a dark-mode visitor never sees a light flash. const themeScript = ` const theme = localStorage.getItem("ui-theme"); if (theme === "dark" || (!theme && window.matchMedia("(prefers-color-scheme: dark)").matches)) { document.documentElement.classList.add("dark-mode"); } `; export const Route = createRootRoute({ head: () => ({ meta: [ { charSet: "utf-8", }, { name: "viewport", content: "width=device-width, initial-scale=1", }, { title: "TanStack Start Starter", }, ], links: [ { rel: "preconnect", href: "https://fonts.googleapis.com" }, { rel: "preconnect", href: "https://fonts.gstatic.com", crossOrigin: "anonymous" }, { rel: "stylesheet", href: "https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,400..700;1,14..32,400..700&display=swap" }, { rel: "stylesheet", href: appCss }, ], scripts: [{ children: themeScript }], }), shellComponent: RootDocument, }); function RootDocument({ children }: { children: React.ReactNode }) { return ( {children} , }, ]} /> ); } ``` The inline script runs before React hydrates, so it adds the `dark-mode` class before the first paint; `suppressHydrationWarning` tells React that the class on `` is expected to differ from the server-rendered markup. Great! You're all set to start using Untitled UI components. If something is missing, you can copy/paste what you need into your project directly from individual [components](/components) pages. **Need help?** Check our [GitHub repository](https://github.com/untitleduico/react/) for examples, or [open an issue](https://github.com/untitleduico/react/issues/) if you run into any problems. Our community is here to help! ## Using Untitled UI components After setting up the providers, you can use Untitled UI components in any route: ```tsx import { createFileRoute } from "@tanstack/react-router"; import { Button } from "@/components/base/buttons/button"; import { Input } from "@/components/base/input/input"; export const Route = createFileRoute("/")({ component: Home, }); function Home() { const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); const formData = new FormData(e.target as HTMLFormElement); const name = formData.get("name") as string; console.log(name); }; return (

Welcome to Untitled UI + TanStack Start

); } ``` ## Dark mode Untitled UI supports dark mode out of the box. The implementation uses CSS variables to provide a seamless experience. ### Theme toggle Create a toggle to switch between light and dark mode: ```tsx import { Moon01, Sun } from "@untitledui/icons"; import { Button } from "@/components/base/buttons/button"; import { useTheme } from "@/providers/theme-provider"; export function ThemeToggle() { const { setTheme } = useTheme(); return (