Skip to content

Theming

The application's CSS is the theme. Klean has no ThemeProvider, theme object, preset code, named theme catalog, or theme section in a configuration file.

Neutral component defaults work without global Klean tokens. Products apply their visual language with ordinary Tailwind, and the copied source remains replaceable.

Three levels of styling

One local treatment: use classes

ApproveInvoiceButton.vue
<script setup>
import Button from '@/components/ui/button/Button.vue'
</script>

<template>
  <Button class="bg-emerald-700 text-white hover:bg-emerald-800">
    Approve invoice
  </Button>
</template>

One repeated product concept: create a component

DeployButton.vue
<!-- assets/js/components/DeployButton.vue -->
<script setup>
import Button from './ui/button/Button.vue'
</script>

<template>
  <Button
    class="min-h-11 bg-emerald-700 px-5 font-semibold text-white hover:bg-emerald-800"
  >
    <slot />
  </Button>
</template>

This is the normal replacement for variant="primary". The product concept receives a product name and stays inside the product. Caller classes still merge last when the product component needs a local adjustment.

One shared value: use Tailwind's theme

css
@import 'tailwindcss';

@theme {
  --color-brand: oklch(0.49 0.16 154);
  --color-on-brand: oklch(0.985 0 0);
}
vue
<Button class="bg-brand text-on-brand hover:bg-brand/90">
  Approve invoice
</Button>

The token creates Tailwind utilities. It does not create component variants.

The Vue, React, and Svelte examples above all consume the same application CSS. Changing frameworks does not create a Klean theme layer.

Optional semantic foundations

Applications with coordinated modes or white-label branding can define a short set of application-owned roles such as canvas, ink, surface, muted, line, and focus. Product signals should have paired foregrounds such as brand / on-brand.

These roles are optional application infrastructure. Klean does not generate them, prompt for them, or require them before a component works.

Avoid component-specific tokens such as --button-primary-hover-background. They recreate a variant matrix in CSS and make the primitive harder to replace.

Light and dark

An application that follows the operating system can use Tailwind's ordinary dark: utilities. A manual switcher can expose one resolved mode on the root:

html
<html data-mode="dark"></html>
css
@custom-variant dark (&:where([data-mode="dark"], [data-mode="dark"] *));

If the stored preference is system, application-owned code resolves the media query and writes light or dark before paint. A server-readable cookie is the right convention when SSR must avoid a flash.

The mode preference is a Durable UI concern because a person chose it. Store that preference with the framework-native useStoredState or createStoredState source from the Durable UI bundle, resolve it before paint, and leave component source unaware of the storage mechanism.

Proving applications, not themes

Hagfish and Slipway intentionally keep different typography, spacing, color, density, shadow, and motion. They prove that Klean's semantic and behavioral source accepts radically different application-owned Tailwind. They are not named themes and do not appear in a theme selector.

Accessibility invariants

Every product mode must preserve:

  • text, icon, boundary, and focus contrast;
  • states communicated by more than color;
  • readable disabled content;
  • reduced-motion behavior independently of theme;
  • native control rendering through the correct color-scheme;
  • useful forced-colors behavior.

Klean does not guess contrast at runtime. Applications choose and test deliberate foreground/background pairs.

The decision rule

QuestionUse
Is this unique here?Direct Tailwind classes
Is this a repeated product concept?Application-owned component
Is this value shared across unrelated surfaces?Tailwind @theme variable
Is this light/dark user state?CSS plus one root mode attribute
Does this change behavior?A behavioral prop

Do not tokenize a value merely because it exists.

All open source projects are released under the MIT License.