Skip to main content

Themes

By the end of this page

A store in your client's exact colours and typography, and an understanding of why changing defaultTheme sometimes appears to do nothing.

Using a built-in theme​

<CatalogApp defaultTheme="coffee" />

That is the whole API. Fifty ids are available — see the theme gallery for all of them with previews, or browse them live in the demo.

Each theme carries its own palette, heading and body fonts, hero layout, card style, nav style, brand name, tagline and hero copy. They are not one template recoloured fifty times.

Building your client's brand​

defineTheme fills in a sensible base and lets you override only what matters.

import { defineTheme } from '@neverleans-labs/plug-store-themes';
import { CatalogApp } from '@neverleans-labs/plug-store-core';

const bloom = defineTheme({
id: 'bloom',
name: 'BLOOM COSMETICS',
tagline: 'Clean beauty, delivered',
colors: {
primary: '340 65% 47%',
primaryForeground: '0 0% 100%',
background: '20 40% 98%',
heroGradientFrom: '340 60% 40%',
heroGradientTo: '20 70% 60%',
},
fonts: {
heading: '"Playfair Display", serif',
body: '"Inter", sans-serif',
},
heroStyle: 'split',
cardStyle: 'rounded',
navStyle: 'elegant',
});

export default function App() {
return <CatalogApp customTheme={bloom} />;
}

id, name and tagline are required; everything else falls back to the base theme. customTheme takes precedence over defaultTheme.

Colours are HSL channels, without hsl()

Every colour is three space-separated values: 340 65% 47%. Not #d92f6a, not hsl(340 65% 47%). They are injected as CSS custom properties and composed by Tailwind as hsl(var(--primary)), so the wrapper has to be absent.

Colour tokens​

TokenUsed for
primaryButtons, active states, badges, price highlights
primaryForegroundText on top of primary
secondarySecondary buttons and surfaces
secondaryForegroundText on secondary
accentHover states, small highlights
accentForegroundText on accent
backgroundPage background
foregroundDefault body text
cardProduct cards, panels
cardForegroundText inside cards
mutedSkeletons, disabled surfaces, chips
mutedForegroundSecondary text
borderAll borders and dividers
heroGradientFromHero gradient start
heroGradientToHero gradient end

Fonts​

fonts: {
heading: '"Playfair Display", serif',
body: '"Inter", sans-serif',
}

PlugStore does not load webfonts for you. Add the <link> to your index.html or @import it in your CSS — otherwise the browser falls back to the next family in the stack.

Layout styles​

heroStyle picks the home page hero:

fullwidth · split · centered · overlay · minimal · energetic · editorial · playful · industrial · gallery

cardStyle picks the product card treatment:

rounded · sharp · elevated · bordered · minimal · tilted · paper · soft · metal · frame

navStyle picks the header treatment:

standard · centered · minimal · bold · elegant

An optional heroImage overrides the hero background image path.

Extending a built-in theme​

Spread one of the shipped themes and change only what the client needs:

themeConfigs is a record keyed by theme id, so look one up by key:

import { themeConfigs, defineTheme } from '@neverleans-labs/plug-store-themes';

const base = themeConfigs.coffee;

const myRoastery = defineTheme({
...base,
id: 'my-roastery',
name: 'SERRA ROASTERS',
tagline: 'Single origin, roasted weekly',
colors: { ...base.colors, primary: '25 70% 38%' },
});

Letting visitors switch themes​

ThemeSwitcher renders a picker over all 50. Useful for a demo or an agency showcase; usually not something a real store ships.

import { ThemeSwitcher } from '@neverleans-labs/plug-store-core';

To read or set the theme yourself:

import { useTheme } from '@neverleans-labs/plug-store-core';

const { theme, template, setTemplate } = useTheme();
// theme → the full ThemeConfig currently applied
// template → its id, e.g. 'coffee'
// setTemplate('bakery') → switches and persists

defaultTheme vs stored theme​

The selected theme is persisted to localStorage under ecom-template, so a returning visitor sees the same store they left.

That created a trap: a visitor who had ever loaded any PlugStore site was pinned to the stored value, and changing defaultTheme in code did nothing for them. The provider now also records which defaultTheme produced the stored value, in ecom-template-default. When you change the prop, the new value wins; when you do not, the visitor's own choice survives.

If you are testing and want a clean slate:

localStorage.removeItem('ecom-template');
localStorage.removeItem('ecom-template-default');

Next​