How I configured themes in ZippyStarter's next.js blog starter

Who likes coming up with naming conventions and standards?

I don't!

I'd rather do the fun stuff, luckily the internet is filled with great people who have done that for you already. This is why I use shadcn/ui, which is where the underlying theme architecture for ZippyStarter's Next.js Blog Starter comes from.

Installation

I added shadcn/ui to an existing project, I ran the shadcn-ui init CLI command to set up the project.

pnpm dlx shadcn-ui@latest init

I was asked a few questions by the CLI:

Would you like to use TypeScript (recommended)? no / yes
Which style would you like to use? › Default
Which color would you like to use as base color? › Slate
Where is your global CSS file? › › app/globals.css
Do you want to use CSS variables for colors? › no / yes
Where is your tailwind.config.js located? › tailwind.config.js
Configure the import alias for components: › @/components
Configure the import alias for utils: › @/lib/utils
Are you using React Server Components? › no / yes

I opted for using CSS variables, a.k.a. custom properties. This enables global styles (for light/dark themes) to be managed in a single file: globals.css, rather than peppering utility classes like :dark (don't do this) throughout the codebase.

I'd already defined a folder structure called @/utils not @/lib/utils so ended up creating a separate @/lib/utils dir. Mainly because when you use the docs it's all about copying and pasting code samples, if you have a different architecture to shadcn/ui you are constantly wasting time reconfiguring all the stuff you copy, it's not wirth the effort!

This is the output from those prompts:

{
  "$schema": "https://ui.shadcn.com/schema.json",
  "style": "default",
  "rsc": true,
  "tsx": true,
  "tailwind": {
    "config": "tailwind.config.js",
    "css": "src/app/globals.css",
    "baseColor": "slate",
    "cssVariables": true
  },
  "aliases": {
    "components": "@/components",
    "utils": "@/utils"
  }
}

I succumbed to most of the conventions he uses, and I'll probably go all in at some point, if I can say goodbye to PascalCase component names and go with kebab-case instead.

Naming conventions

This section isn't about some random face-to-face networking events about naming things, it's about how shadcn/ui names CSS custom properties, something that is really important to understand.

If you misunderstand this part (like I did at first) you're entire codebase will be littered with inappropriate CSS classes that will make everything look wrong and be impossible to manage.

We use a simple background and foreground convention for colors. The background variable is used for the background color of the component and the foreground variable is used for the text color.

shadcn on conventions

Given the following CSS variables:

/* The background suffix is omitted when the variable is used for the
 * background color of the component.
 */
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;

The background color of the following component will be hsl(var(--primary)) and the foreground color will be hsl(var(--primary-foreground)).

<div className="bg-primary text-primary-foreground">Hello</div>

If you misunderstand what primary & secondary are for, and use them for things like backgrounds and paragraphs you'll come unstuck, traditionally they are associated with buttons, that's also the case with shadcn/ui.

List of variables

Here's the list of variables available for customization:

/* Default background color of <body />...etc */
--background: 0 0% 100%;
--foreground: 222.2 47.4% 11.2%;
 
/* Muted backgrounds such as <TabsList />, <Skeleton /> and <Switch /> */
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
 
/* Background color for <Card /> */
--card: 0 0% 100%;
--card-foreground: 222.2 47.4% 11.2%;
 
/* Background color for popovers such as <DropdownMenu />, <HoverCard />, <Popover /> */
--popover: 0 0% 100%;
--popover-foreground: 222.2 47.4% 11.2%;
 
/* Default border color */
--border: 214.3 31.8% 91.4%;
 
/* Border color for inputs such as <Input />, <Select />, <Textarea /> */
--input: 214.3 31.8% 91.4%;
 
/* Primary colors for <Button /> */
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;
 
/* Secondary colors for <Button /> */
--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;
 
/* Used for accents such as hover effects on <DropdownMenuItem>, <SelectItem>...etc */
--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;
 
/* Used for destructive actions such as <Button variant="destructive"> */
--destructive: 0 100% 50%;
--destructive-foreground: 210 40% 98%;
 
/* Used for focus ring */
--ring: 215 20.2% 65.1%;
 
/* Border radius for card, input and buttons */
--radius: 0.5rem;

Adding new colors

You can add more colors as you wish, after all they're only custom properties. I added some for the code samples I use, which don't theme switch currently.

To add new colors, you need to add them to your CSS file and to your tailwind.config.js file.

app/globals.css
:root {
  --warning: 38 92% 50%;
  --warning-foreground: 48 96% 89%;
}
 
.dark {
  --warning: 48 96% 89%;
  --warning-foreground: 38 92% 50%;
}
tailwind.config.js
module.exports = {
  theme: {
    extend: {
      colors: {
        warning: "hsl(var(--warning))",
          "warning-foreground": "hsl(var(--warning-foreground))",
      },
    },
  },
}

You can now use the warning utility class in your components.

<div className="bg-warning text-warning-foreground" />

Color formats

shadcn/ui recommends using hsl color formats, which I do, you can use other formats if you prefer.