Skip to content

Theming and customization

One source of design: --ui-* tokens for color, size and shape, which you can connect to your brand.

Light, dark and system

theme belongs to the framework. It stores the preference and applies data-theme before the first paint. UIThemeToggle lets readers change it, also without JavaScript. Tokens change by themselves: do not write rules per theme.

The palette

Each scale has 12 steps: 1 and 2 for backgrounds, 3 to 5 for controls, 6 to 8 for borders, 9 and 10 for solids, 11 and 12 for text. The background is pure white in light and pure black in dark. Violet is for the main action, the focus, the selection and what is active: if everything is violet, nothing stands out.

--ui-color-primary-*

--ui-color-surface-*

--ui-color-info-*

--ui-color-success-*

--ui-color-warning-*

--ui-color-danger-*

Usage tokens

Read the usage token, not the step: --ui-color-border, not --ui-color-surface-6. No loose hex, size or font: if you miss something, it is added to UI. The whole contract is in packages/ui/DESIGN.md.

app/styles/main.css css
.release-note {
  border: var(--ui-border-width) solid var(--ui-color-border);
  border-radius: var(--ui-radius-lg);
  background: var(--ui-color-surface);
  padding: var(--ui-space-4);
  color: var(--ui-color-text-soft);
  font-size: var(--ui-font-size-md);
}
Main tokens
Name Type / default Description
--ui-color-bg / -bg-subtle color Background of the page and quiet bands.
--ui-color-surface / -surface-raised color Card, panel and table; popover and modal.
--ui-color-control / -hover / -active color Background of a control and its states.
--ui-color-text / -text-soft color Text and secondary text.
--ui-color-border / -border-strong / -control-border color Separators, quiet borders and the border of a field at 3:1 or more.
--ui-color-primary / -hover / -contrast color Main action and the text on it.
--ui-color-primary-soft / -text / -border color A label or a quiet active background, with its text and its border.
--ui-color-danger / -soft / -border / -text color Info, success and warning have the same shape.
--ui-font-size-xs … -6xl rem Text sizes: reading at lg, controls at md, titles from 2xl up.
--ui-space-* / --ui-radius-* rem Space in steps of 0.25rem and radii from xs to xl.
--ui-control-height-sm / -md / -lg rem Heights of the controls, which grow by themselves with a touch pointer.
--ui-duration-* / --ui-ease-* time Motion: fast for color, base and slow for a panel.

Fluid scale

The interface grows with the screen in one place, the global base: 16 px up to 1440, 18 px at 2560 and 21 px at 3840, always from the font size of the browser. Every token is in rem, so they grow together. What is written in px does not grow. ui: { base: false } removes the base, and the scale with it.

Your brand

The sheets of app/styles load after the module: override the steps of a scale and everything that reads them changes with them.

app/styles/main.css css
/* app/styles/main.css: your brand, over the steps of each scale */
:root {
  --ui-color-primary-9: #0a7cff;
  --ui-color-primary-10: #0067d6;
  --ui-radius-md: 0.5rem;
}
:root[data-theme='dark'] {
  --ui-color-primary-9: #3b96ff;
  --ui-color-primary-10: #66adff;
}

If your site already has its variables, ui.tokens accepts CSS values or references to them, and each key assigns its --ui-* tokens.

meeko.config.ts ts
import { defineMeekoConfig } from '@meekojs/meeko/framework'

export default defineMeekoConfig({
  modules: ['@meekojs/ui'],
  theme: { default: 'dark' },
  ui: {
    // Each key assigns --ui-* tokens; a var() reference follows the themes of your site.
    tokens: {
      primary: 'var(--brand-action)',
      primaryHover: 'var(--brand-action-hover)',
      onPrimary: 'var(--brand-on-action)',
      focus: 'var(--brand-focus)',
      surface: 'var(--brand-surface)',
      text: 'var(--brand-text)',
      border: 'var(--brand-border)',
    },
    defaults: {
      button: { shape: 'pill', size: 'md' },
      link: { variant: 'outline' },
      card: { variant: 'soft' },
    },
  },
})
Keys of ui.tokens
Name Type / default Description
primary / primaryHover / onPrimary string --ui-color-primary, its hover and the text on it.
accentSoft / accentText string Backgrounds and text of active states: --ui-color-primary-soft and -text.
surface / surfaceHover string Surfaces and controls, and their states.
text / textSoft / muted string Main and secondary text.
border / borderStrong string Separators and outlines.
focus / danger string Keyboard focus and errors.
font / radius string Font family and base radius.
controlHeight / controlPadding string Dimensions of the actions.
controlWeight / controlFontSize string Weight and text size of the actions.

Default values

ui.defaults accepts button, link and card. The priority is: explicit props, application defaults and base values. Defaults are isolated per application and SSR request.

Texts and languages

The few texts components paint on their own (the close button of UIModal, the empty state of UITable and UICombobox, the names of UIThemeToggle) come in Spanish and English. With @meekojs/i18n they follow the language of the document, without any configuration. Without that module, ui.locale chooses the language; Spanish by default. ui.messages overrides texts or adds other languages.

ts
// meeko.config.ts
export default defineMeekoConfig({
  modules: ['@meekojs/ui'],
  ui: {
    // Without @meekojs/i18n, the language of the texts; with it, the fallback of a language without texts.
    locale: 'en',
    messages: {
      en: { table: { empty: 'Nothing here yet.' } },
      fr: {
        modal: { close: 'Fermer' },
        table: { empty: 'Aucune donnée.' },
        select: { empty: 'Aucun résultat', clear: 'Effacer la sélection' },
        themeToggle: {
          labels: { light: 'Clair', dark: 'Sombre', system: 'Système' },
          unknown: 'Changer de thème',
          group: 'Thème',
          cycle: 'Thème : {current}. Passer à {next}',
        },
      },
    },
  },
})

A prop or a snippet of the component always win over these texts. What a language lacks comes from its base language (pt for pt-BR), from ui.locale and, last, from Spanish. The server and the client choose the same text, so hydration changes nothing.

The types UIOptions, UITokens and UIDefaults are exported from @meekojs/ui.

Was this page helpful?

Add a comment

It is sent when you choose “Yes” or “No”. Only the page, your answer and the comment are stored: no cookies and no personal data.

0 of 2000 characters