@akhatmirzo/ui

Theming and tokens

Colors, typography, radii, shadows, motion and how to change the brand color.

The whole design lives in one file — @akhatmirzo/tokens/theme.css. It holds CSS variables (--ak-*) and an @theme block that turns them into Tailwind utilities. Components never use raw colors like indigo-600 — only semantic tokens such as bg-primary and text-text-muted. That is why dark mode and the brand color change in one place.

app.css
@import 'tailwindcss';
@import '@akhatmirzo/tokens/theme.css';

Tokens come in two layers:

  1. Base scales — --ak-indigo-600, --ak-slate-200 and so on. They exist to build the semantic tokens. Don't use them directly in components or app code.
  2. Semantic tokens — --ak-surface, --ak-text-muted, --ak-primary and so on. Each one points to a different base value in light and dark mode, and shows up in Tailwind as bg-surface, text-text-muted, bg-primary.

Color palette

The accent is indigo and the neutral is slate. Status colors are emerald (success), amber (warning), rose (danger) and sky (info). The values below are read straight from theme.css.

indigo
50
100
200
300
400
500
600
700
800
900
950
slate
50
100
200
300
400
500
600
700
800
900
950
emerald
50
100
300
400
500
600
700
950
amber
50
100
300
400
500
600
700
950
rose
50
100
300
400
500
600
700
950
sky
50
100
300
400
500
600
700
950

Semantic tokens

Pick from this list when you write code. Switch the site theme — the swatches and values follow dark mode.

backgroundSahifa foni—
surfaceKarta, input, modal—
surface-mutedHover, ikkilamchi fon—
surface-raisedPopover, toast—
borderOddiy chegara—
border-strongHover chegara—
textAsosiy matn—
text-subtleGhost tugma, ikonka—
text-mutedYordamchi matn—
text-faintPlaceholder—
primaryAsosiy tugma—
primary-softSoft tugma, badge—
primary-soft-foregroundSoft ustidagi matn—
linkHavola—
dangerXavfli tugma—
ringFokus halqasi—
TokenTailwindLightDarkUsed for
backgroundbg-backgroundslate-50slate-900Page background
surfacebg-surfacewhiteslate-800Cards, inputs, modals
surface-mutedbg-surface-mutedslate-100slate-700Hover, secondary fill
surface-raisedbg-surface-raisedwhiteslate-750Popovers, toasts, menus
borderborder-borderslate-200slate-700Default border
border-strongborder-border-strongslate-300slate-600Hover border
texttext-textslate-900slate-100Body text
text-subtletext-text-subtleslate-700slate-300Ghost buttons, icons
text-mutedtext-text-mutedslate-500slate-400Secondary text
text-fainttext-text-faintslate-400slate-500Placeholders
primarybg-primaryindigo-600indigo-600Primary button
primary-hoverbg-primary-hoverindigo-700indigo-500Primary button hover
primary-foregroundtext-primary-foregroundwhitewhiteText on primary
primary-softbg-primary-softindigo-50indigo-950Soft buttons, badges
primary-soft-hoverbg-primary-soft-hoverindigo-100indigo-900Soft button hover
primary-soft-foregroundtext-primary-soft-foregroundindigo-700indigo-300Text on soft
linktext-linkindigo-600indigo-300Links
ringoutline-ringindigo-500 / 40%indigo-400 / 50%Focus ring
overlaybg-overlayslate-900 / 40%slate-950 / 60%Backdrop behind modals
dangerbg-dangerrose-600rose-500Destructive button
danger-hoverbg-danger-hoverrose-700rose-600Destructive button hover
danger-ringoutline-danger-ringrose-500 / 25%rose-500 / 35%Focus in error state

Primary stays the same in dark mode

The primary button is indigo-600 in both modes. Indigo-500 with white text falls short of WCAG AA contrast, so in dark mode only the hover state gets lighter.

Status colors

Each status has two sets: soft for badge and message backgrounds, solid for dots and fully filled badges.

softsuccesswarningdangerinfo
solidsuccesswarningdangerinfo
StatusSoftSolid
successbg-success-soft text-success-soft-foregroundbg-success text-success-foreground
warningbg-warning-soft text-warning-soft-foregroundbg-warning text-warning-foreground
dangerbg-danger-soft text-danger-soft-foregroundbg-danger text-danger-foreground
infobg-info-soft text-info-soft-foregroundbg-info text-info-foreground

Typography

The main typeface is Manrope (Latin and Cyrillic); code uses JetBrains Mono. theme.css only names the fonts (font-sans, font-mono) — it doesn't load them. Add them yourself:

pnpm add @fontsource-variable/manrope @fontsource-variable/jetbrains-mono
app.css
@import '@fontsource-variable/manrope';
@import '@fontsource-variable/jetbrains-mono';

Without the fonts, the system font (ui-sans-serif, system-ui) takes over. The size scale ships with line heights and letter spacing:

text-4xl36 / 40

O'zbekiston — Ўзбекистон

text-3xl30 / 36

O'zbekiston — Ўзбекистон

text-2xl24 / 32

O'zbekiston — Ўзбекистон

text-xl20 / 28

O'zbekiston — Ўзбекистон

text-lg18 / 28

O'zbekiston — Ўзбекистон

text-base16 / 24

O'zbekiston — Ўзбекистон

text-sm14 / 20

O'zbekiston — Ўзбекистон

text-xs12 / 16

O'zbekiston — Ўзбекистон

font-mono: 1 250 000 so'm · +998 90

Font weight isn't tied to size — each component sets its own font-medium or font-semibold. As a rule of thumb: buttons and labels 500, headings 600, large headings 700. Add tabular-nums to tables and prices so the digits line up.

Radii and shadows

Buttons and inputs use rounded-lg (12px), cards and popovers rounded-xl (16px), modals and drawers rounded-2xl (20px). Shadows are tinted slate-900 instead of black, which keeps them soft. In dark mode, shadow-md and up add a 1px ring, because a shadow alone barely shows on a dark background.

rounded-sm6px
rounded-md8px
rounded-lg12px
rounded-xl16px
rounded-2xl20px
rounded-full9999px
shadow-xsInput, tugma
shadow-smKarta
shadow-mdDropdown
shadow-lgPopover, toast
shadow-xlModal, drawer

Motion

Motion carries information, not decoration: it shows where an element came from and where it went. Entrances are slower and use ease-out; exits are faster and use ease-in.

ease-outKirishlar (asosiy)
ease-inChiqishlar
ease-in-outJoyida o'zgarish
ease-springToast, switch
DurationValueUsed for
duration-fast100msHover, press, closing a dropdown
duration-base150msOpening a dropdown, tooltips
duration-moderate200msOpening a modal, accordions, tabs
duration-slow300msDrawers, toast entrance

Easings: ease-out (the default), ease-in, ease-in-out and ease-spring (use sparingly — toasts, switches). There are ready keyframes for overlays too: animate-dialog-in/out, animate-popover-in/out, animate-tooltip-in/out, animate-slide-in-right (plus left, top, bottom), animate-toast-in/out, animate-accordion-down/up, as well as animate-spinner and animate-shimmer.

prefers-reduced-motion

When the user turns on "reduce motion" in their OS, the popover slide is switched off. Use the motion-reduce: variant in your own animations.

Layers (z-index)

These aren't mapped to Tailwind utilities — use the CSS variable: z-(--ak-z-modal).

VariableValueWho uses it
--ak-z-dropdown1000Dropdown, Select, Combobox, Popover
--ak-z-overlay1100Dialog, Drawer and Command Palette backdrop
--ak-z-modal1110Dialog, Drawer and Command Palette panel
--ak-z-toast1200Toast
--ak-z-tooltip1300Tooltip

Changing the brand color

The brand color is the --ak-primary* group. Redefine it after theme.css — the components don't need to change. In the example below the brand color changes inside a single block.

Yangi

To change it across the app, write the light and dark values separately. Any color from the base scales works, and so do your own hex values:

app.css
@import 'tailwindcss';
@import '@akhatmirzo/tokens/theme.css';

:root,
[data-theme='light'] {
  --ak-primary: var(--ak-emerald-600);
  --ak-primary-hover: var(--ak-emerald-700);
  --ak-primary-soft: var(--ak-emerald-50);
  --ak-primary-soft-hover: var(--ak-emerald-100);
  --ak-primary-soft-foreground: var(--ak-emerald-700);
  --ak-ring: rgb(16 185 129 / 0.4);
  --ak-link: var(--ak-emerald-700);
}

[data-theme='dark'] {
  --ak-primary: var(--ak-emerald-600);
  --ak-primary-hover: var(--ak-emerald-500);
  --ak-primary-soft: var(--ak-emerald-950);
  --ak-primary-soft-hover: var(--ak-emerald-950);
  --ak-primary-soft-foreground: var(--ak-emerald-300);
  --ak-ring: rgb(52 211 153 / 0.5);
  --ak-link: var(--ak-emerald-300);
}

Write the dark block as well

A :root value on <html> also wins in dark mode: both selectors have the same specificity and your file comes later. But tokens that differ between modes (soft, ring, link) and nested data-theme="dark" blocks need a complete [data-theme='dark'] block.

Text on primary is white (--ak-primary-foreground). If you pick a light color such as amber, check the contrast and change --ak-primary-foreground too if needed.

Tailwind utilities

Besides colors, radii and shadows, theme.css adds a few ready utilities:

UtilityWhat it does
focus-ringA 2px ring on keyboard focus only (:focus-visible)
focus-ring-withinFor input containers: border and ring when something inside has focus; danger on error
duration-fast … slowDuration tokens (pair with transition-*)
ease-out, ease-spring ...Easing tokens
dark:Works inside data-theme="dark" (not class="dark")
In your own component
<button className="focus-ring rounded-lg bg-primary-soft px-4 py-2 text-sm font-medium text-primary-soft-foreground transition-colors duration-base hover:bg-primary-soft-hover">
  Filter
</button>

Outside Tailwind (your own CSS, inline styles, a chart library) colors, shadows, durations, easings and layers are available as CSS variables:

.chart-tooltip {
  background: var(--ak-surface-raised);
  color: var(--ak-text);
  box-shadow: var(--ak-shadow-lg);
  transition: opacity var(--ak-duration-base) var(--ak-ease-out);
  z-index: var(--ak-z-tooltip);
}

On this page