Colors and Branding

One of the main features of theui-svelte components library is its customizable color options, allowing you to maintain brand identity and ensure consistent theming efficiently.

On top of Tailwind's own palette, the library defines a set of brand and status colors in a @theme block. They behave exactly like Tailwind's native color classes, shades included, and every component reads from them, so changing one variable restyles the whole library.

Available Color Classes

The library exposes these colors as utility classes you can use in your own markup. Here is the full list:

Shaded Colors

 
50
100
200
300
400
500
600
700
800
900
950

Brand primary --color-brand-[S]

Error --color-error-[S]

Info --color-info-[S]

Success --color-success-[S]

Warning --color-warning-[S]

Non-shaded Colors

  Light mode Dark mode
Background colors Primary (bg-primary) --background-color-primary
Secondary (bg-secondary) --background-color-secondary
Tertiary (bg-tertiary) --background-color-tertiary
Alternate background (bg-alt) --background-color-alt
Text colors Default (text-default) --text-color-default
Text on brand (text-on-brand) --text-color-on-brand
Muted (text-muted) --text-color-muted
Alternate text (text-alt) --text-color-alt

What Each Color Controls

Before changing anything, it helps to know which token does what. Most of the library runs on a handful of them.

Token Where you see it
--color-brand-* Primary buttons, the active tab, the selected day of a calendar, the filled part of a range, wizard steps, links and every focus ring. Shade 500 carries most of it, with 600 for hover and 300 for lighter accents.
--text-color-on-brand The text and icons that sit on top of a brand background. It is one value, not a scale.
--color-error-*, info, success, warning Alerts, badges, notifications, and the states of a form field.
--background-color-* Surfaces: the page (primary), cards, menus and panels (secondary), the layer above those (tertiary), and the inverted one (alt).
--text-color-* Body text (default), secondary text such as helper text and placeholders (muted), and text on the inverted surface (alt).

So a rebrand is usually two things: the brand scale, and the foreground that goes on top of it. Everything else follows.

Rebrand In One Step

The library's own brand scale is nothing but aliases of Tailwind's rose palette. Point those eleven variables at another Tailwind palette and every component moves with it.

./src/app.css
@import 'tailwindcss';
@import 'theui-svelte/style';

@theme {
  --color-brand-50: var(--color-indigo-50);
  --color-brand-100: var(--color-indigo-100);
  --color-brand-200: var(--color-indigo-200);
  --color-brand-300: var(--color-indigo-300);
  --color-brand-400: var(--color-indigo-400);
  --color-brand-500: var(--color-indigo-500);
  --color-brand-600: var(--color-indigo-600);
  --color-brand-700: var(--color-indigo-700);
  --color-brand-800: var(--color-indigo-800);
  --color-brand-900: var(--color-indigo-900);
  --color-brand-950: var(--color-indigo-950);
}

Your own palette works the same way, with values in place of the aliases. Any CSS color will do, though oklch is what Tailwind itself uses, and it keeps the steps between shades even.

./src/app.css
@theme {
  --color-brand-500: oklch(0.62 0.19 264);
  --color-brand-600: oklch(0.55 0.19 264);
  /* ... and the rest of the scale */
}

Where you put this matters. The override has to come after @import 'theui-svelte/style'. Both declarations land in the same layer, so the later one wins; put it above the import and nothing changes.

Check the contrast while you are here. --text-color-on-brand is white, which suits a mid or dark brand color. Pick a light one, such as amber or lime, and every primary button becomes white on light, which nobody can read. Set the foreground with the brand, aiming for at least 4.5:1 against shade 500; the accessibility guide covers the targets.

./src/app.css
@theme {
  --color-brand-500: var(--color-amber-400);
  --text-color-on-brand: #16161D;   /* dark text on a light brand */
}

Colors In Dark Mode

Surfaces and text colors are declared twice: once for light mode in @theme, and again inside .dark in the base layer. The base layer comes after the theme layer, so the dark values win whenever the dark class is on the page. An override in @theme alone therefore changes light mode only, and the change appears to vanish the moment you switch to dark.

To change both, write both. Surfaces and text go in two places; the brand and status scales are not re-declared for dark mode, so those need the @theme block only.

./src/app.css
@import 'tailwindcss';
@import 'theui-svelte/style';

@theme {
  /* light mode, and the brand scale for both modes */
  --color-brand-500: var(--color-indigo-500);
  --background-color-primary: #ffffff;
  --background-color-secondary: #f4f4f7;
  --text-color-muted: #5a5a63;
}

@layer base {
  .dark {
    /* dark mode only */
    --background-color-primary: #0b0b10;
    --background-color-secondary: #14141b;
    --text-color-muted: #9b9ba3;
  }
}

The dark class itself belongs on the <html> element. The DarkMode component puts it there and remembers the choice.

Reskin Every Surface At Once

Behind the surface and text tokens sit eight raw values. Six are the surfaces, three for light and three for dark: every bg-* token is built from those, and text-default and text-alt borrow from them too. That makes them the shortest way to change the feel of a whole app without touching a single component.

The other two are --text-muted-on-light and --text-muted-on-dark, which feed --text-color-muted, the color of the text-muted class: helper text, placeholders, timestamps and other secondary text. They stand apart from the six because muted text is not a surface. It has to stay readable against the page while still sitting back from the body text, and the value that does that in light mode is not the one that does it in dark mode. The library picks the right one for the current mode, so you set both and think no further about it.

./src/app.css
:root {
  --theui-light1: #FFFFFF;   /* the page */
  --theui-light2: #F4F4F7;   /* cards, menus, panels */
  --theui-light3: #E8E8EE;   /* the layer above those */
  --theui-dark1: #0B0B10;    /* the page, in dark mode */
  --theui-dark2: #14141B;
  --theui-dark3: #1C1C26;
  --text-muted-on-light: #5A5A63;
  --text-muted-on-dark: #9B9BA3;
}

These are plain CSS variables rather than theme tokens, so they build no utilities of their own; they only feed the tokens above. That also makes them simpler to override than a theme token: they live on :root, so a :root block of your own in app.css replaces them, with no @theme and no separate rule for dark mode. Redefine them after the import, as with everything else on this page.

Set only the ones you want to move. Anything you leave out keeps the library's value, so changing --text-muted-on-dark alone is a perfectly good edit.

Modify Existing Colors

You can override the library's default colors by assigning new values to the corresponding CSS theme variables:

./src/app.css
@theme {
  /* Modify brand primary colors */
  --color-brand-50: #f0f9ff;
  --color-brand-100: #e0f2fe;
  --color-brand-200: #bae6fd;
  --color-brand-300: #7dd3fc;
  --color-brand-400: #38bdf8;
  --color-brand-500: #0ea5e9;
  --color-brand-600: #0284c7;
  --color-brand-700: #0369a1;
  --color-brand-800: #075985;
  --color-brand-900: #0c4a6e;
  --color-brand-950: #082f49;

  /* Modify background colors */
  --background-color-primary: #ffffff;
  --background-color-secondary: #f8fafc;
  
  /* Modify text colors */
  --text-color-default: #1e293b;
  --text-color-muted: #64748b;
}

Add New Color

Colors of your own go in the same @theme block, and the name you choose decides which utilities Tailwind builds from it. Get the prefix wrong and nothing is reported: the variable is defined, the class simply never exists.

Add Shaded Colors

A name under --color-* gives you the whole family of utilities for that color: background, text, border, ring, fill, and the rest.

./src/app.css
@theme {
  --color-accent-50: #f0f9ff;
  --color-accent-100: #e0f2fe;
  /* ... add more shades as needed */
  --color-accent-500: #0ea5e9;
  --color-accent-950: #082f49;
}

Use them as bg-accent-500, text-accent-300, border-accent-700, ring-accent-500.

Add Non-shaded Colors

A single color for one purpose belongs under the namespace of the property it serves: --background-color-* for backgrounds and --text-color-* for text, the same namespaces this library uses for its own surfaces. --background-* and --text-* are not color namespaces, and --text-* in particular belongs to font sizes, so a color defined there quietly produces nothing.

./src/app.css
@theme {
  /* Right: these build bg-surface and text-highlight */
  --background-color-surface: #f8fafc;
  --text-color-highlight: #4338ca;

  /* Wrong: no utility comes out of either of these */
  --background-surface: #f8fafc;
  --text-highlight: #4338ca;
}

Then write bg-surface and text-highlight as you would any other utility. The same pattern holds for --border-color-*, --fill-* and --ring-color-*.

Remove A Color

To completely remove a color from your theme, set its namespace to initial:

./src/app.css
@theme {
  /* Remove the warning color palette */
  --color-warning-*: initial;
  
  /* Remove specific non-shaded colors */
  --background-color-tertiary: initial;
  --text-color-alt: initial;
}

This will remove all the warning color classes, bg-tertiary and text-alt class from the application!

Available Color Palette

This is what theui-svelte/style.css defines. The brand and status scales are aliases of Tailwind's own palettes, which is why pointing one at another palette takes a single line each.

theui-svelte/style.css
:root {
  /* The raw surface values every light and dark background is built from */
  --theui-light1: #FFFDFD;
  --theui-light2: #F5F3F3;
  --theui-light3: #EBE9E9;
  --theui-dark1: #16161D;
  --theui-dark2: #1E1E26;
  --theui-dark3: #26262F;
  --text-muted-on-light: #636369;
  --text-muted-on-dark: #A1A1AA;
}

@theme {
  /* Brand: 50 to 950, aliased to Tailwind's rose scale */
  --color-brand-50: var(--color-rose-50);
  --color-brand-100: var(--color-rose-100);
  --color-brand-200: var(--color-rose-200);
  --color-brand-300: var(--color-rose-300);
  --color-brand-400: var(--color-rose-400);
  --color-brand-500: var(--color-rose-500);
  --color-brand-600: var(--color-rose-600);
  --color-brand-700: var(--color-rose-700);
  --color-brand-800: var(--color-rose-800);
  --color-brand-900: var(--color-rose-900);
  --color-brand-950: var(--color-rose-950);

  /* What text and icons use on top of a brand background */
  --text-color-on-brand: #FFFFFF;

  /* Status colors, each aliased the same way across all 11 shades */
  --color-error-50 ... --color-error-950:   var(--color-red-*);
  --color-info-50 ... --color-info-950:     var(--color-sky-*);
  --color-success-50 ... --color-success-950: var(--color-green-*);
  --color-warning-50 ... --color-warning-950: var(--color-yellow-*);

  /* Text */
  --text-color-default: var(--theui-dark1);
  --text-color-muted: var(--text-muted-on-light);
  --text-color-alt: var(--theui-light1);

  /* Surfaces */
  --background-color-primary: var(--theui-light1);
  --background-color-secondary: var(--theui-light2);
  --background-color-tertiary: var(--theui-light3);
  --background-color-alt: var(--theui-dark1);
}

/* Dark mode swaps the same names, so every component follows */
@layer base {
  .dark {
    --text-color-default: var(--theui-light2);
    --text-color-muted: var(--text-muted-on-dark);
    --text-color-alt: var(--theui-dark1);

    --background-color-primary: var(--theui-dark1);
    --background-color-secondary: var(--theui-dark2);
    --background-color-tertiary: var(--theui-dark3);
    --background-color-alt: var(--theui-light2);
  }
}

The status scales are written out in full in the stylesheet; they are shortened here because every shade follows the same pattern. Read the file in the GitHub repository for the literal source.