Tucano v0.37.2

Theme

Color, radius, border width, field height, font and motion go through CSS variables prefixed with --tuc-, and all of them live in :root. Changing the look means overriding variables, with nothing to recompile: no color, radius, border or height value is hard-coded in the components' CSS.

Accent color for this page

See tokens

In the swatches, each color also changes --tuc-accent-fg, and --tuc-accent-text too — the accent shade used as text color in the active menu item and in the select's tag. Changing only --tuc-accent would leave unreadable text on the primary button.

:root {
  --tuc-accent: #4f46e5;
  --tuc-accent-hover: #4338ca;
  --tuc-accent-fg: #ffffff;       /* text on top of the accent */
  --tuc-accent-text: #4338ca;     /* accent as text color, in light mode */
  --tuc-radius: 1rem;             /* rounder corners */
  --tuc-control-height: 2.5rem;   /* taller fields */
}
.dark {
  --tuc-accent-text: #818cf8;     /* in dark mode, a lighter shade for contrast */
}

The accent and the text on it

The default accent is neutral: black in light mode and near-white in dark mode. The color belongs to your project — the package doesn't impose a brand, and it fits any brand until you change it. The orange you see in this documentation is the site's color, set the same way you would set yours.

The accent has a separate token for each role, because a bright color rarely works for all of them. On this site's orange, text is white (--tuc-accent-fg), with a contrast of 2.7 — a brand choice; if you need 4.5, use #0a0a0a (7.4). And bright orange as text color on a light background also gives 2.7, so links, the active menu item, the select's tag, "today" in the calendar and active icons read --tuc-accent-text, here #B84300 in light mode (5.5 on white) and #FF7501 itself in dark mode (6.7).

/* This site's orange */
:root {
  --tuc-accent: #FF7501;
  --tuc-accent-hover: #FF8A2A;
  --tuc-accent-fg: #ffffff;
  --tuc-accent-text: #B84300;
  --tuc-thumb: #ffffff;
}
.dark {
  --tuc-accent: #FF7501;
  --tuc-accent-hover: #FF8A2A;
  --tuc-accent-fg: #ffffff;
  --tuc-accent-text: #FF7501;
  --tuc-thumb: #ffffff;
}

.dark repeats the accent because the neutral default changes with the theme: without it, dark mode would go back to near-white.

What gets paintedToken
Background, border, check mark, underline--tuc-accent
The color property of accented text--tuc-accent-text
Text and icons on top of the accent--tuc-accent-fg
The switch knob--tuc-thumb — separate, so whoever switches --tuc-accent-fg to dark doesn't get a black knob

When you change the accent, check three contrasts

The text on top of it (--tuc-accent-fg on --tuc-accent), and the accent text on white, on --tuc-hover and on --tuc-accent-soft. A light color calls for a dark --tuc-accent-fg; a dark color, white.

Semantic tones never read the accent. Success, warning, danger and info have their own tokens, because the brand can be any color and the meaning must not change with it. That's why info got --tuc-info: reading the accent, with orange the info alert looked the same as the warning one, and the "New" badge was indistinguishable from "Under review".

Tokens

The complete list, with the value for each theme. The source of truth is src/styles/core/tokens.css; llms.txt has the same list, generated from the code.

Surface and text

TokenLightDarkControls
--tuc-bg#ffffff#171717Background of fields, panels and dialogs
--tuc-fg#0a0a0a#fafafaMain text
--tuc-muted#737373#a3a3a3Secondary text: hint, table header, timeline time
--tuc-subtle#a3a3a3#737373Faded text and icons: days outside the month, sort arrow, the timeline's neutral dot
--tuc-border#e5e5e5#2e2e2eLines and borders
--tuc-hover#f5f5f5#262626Background under the cursor
--tuc-elevated#fafafa#1f1f1fBackground of inner blocks: table header, zebra stripes, neutral badge

Accent

TokenLightDarkControls
--tuc-accent#0a0a0a#fafafaPrimary button, selected day, checked box, active tab, focus border
--tuc-accent-hover#262626#e5e5e5Primary button under the cursor
--tuc-accent-fg#ffffff#0a0a0aText and icons on top of the accent
--tuc-accent-text#0a0a0a#fafafaAccent as text color: link, active menu, select tag, "today", table avatar
--tuc-thumb#ffffff#171717Switch knob
--tuc-accent-soft12% of the accentsameRange band, select tag, selected table row, avatar background
--tuc-accent-ring35% of the accentsameFocus ring of fields and buttons

Semantic tones

TokenLightDarkControls
--tuc-success#16a34a#4ade80Success badge, alert, toast and timeline
--tuc-warning#d97706#fbbf24The same, for warning
--tuc-danger#dc2626#f87171The same, for danger; also the border and message of a field with an error
--tuc-info#1d4ed8#60a5faThe same, for info — 6.7 on white and 5.6 on its own soft background
--tuc-danger-fill#dc2626sameSolid fill: danger button
--tuc-success-soft12% of the tone12% of the toneSoft badge background
--tuc-warning-soft14% of the tone14% of the toneSoft badge background
--tuc-danger-soft12% of the tone12% of the toneSoft badge background
--tuc-info-soft12% of the tone12% of the toneSoft badge background

Text tone and solid fill are different tokens on purpose. --tuc-danger is lighter in dark mode to have contrast on a dark background — and a button painted with it would look pastel with white text on top. --tuc-danger-fill is the same in both themes.

Shape and size

TokenDefaultUp to 40remControls
--tuc-radius0.875rem—Corners of panels and inner blocks
--tuc-radius-md0.625rem—Controls and day cells
--tuc-radius-sm0.5rem—Buttons and options
--tuc-radius-xs0.375rem—Tags and details
--tuc-border-width1px—Width of every border in the package
--tuc-control-height2.375rem2.75remHeight of field, button, select, color field, segmented tabs and pagination
--tuc-swatch2rem2.5remSwatch, value and eyedropper inside the color picker
--tuc-cell2.25rem—Calendar day cell
--tuc-text0.8125rem1rem in fieldsText size
--tuc-fontinherit—Font family — by default, the project's

Depth and motion

TokenLightDarkControls
--tuc-ringrgb(0 0 0 / 0.12)rgb(255 255 255 / 0.14)Thin outline of panels, part of the shadow
--tuc-shadowthree layersthree layers, denserElevation of calendar, select, color picker, menu and toast
--tuc-easecubic-bezier(0.16, 1, 0.3, 1)sameEntry curve: decelerates to a stop
--tuc-ease-incubic-bezier(0.4, 0, 0.9, 0.3)sameExit curve: accelerates until it disappears
--tuc-duration160mssamePanels and state changes
--tuc-duration-lg280mssameWhat crosses the screen: modal, drawer, toast
--tuc-duration-out170mssameExits

Entry and exit use different curves on purpose: using the same curve for both makes the exit feel sluggish.

Dark mode

Dark mode follows the .dark class on <html>, the convention of Tailwind and of Django's modern admin. data-theme="dark" works too.

<html class="dark">
<html data-theme="dark">

To follow the operating system, mark the root with data-tuc-theme="auto". It's opt-in on purpose: following the system by default made the component turn dark on its own in a light page. The project decides the theme, not the operating system.

<html data-tuc-theme="auto">

Dark mode also sets color-scheme: dark, which makes the browser draw scrollbars and native fields in a dark tone — only inside the components, so it doesn't force the color scheme on the whole page.

Scoping by container

Since they are variables, the scope is up to you: in :root they apply to the whole page, and declared on a container they apply only inside it. That's how you get a panel with its own color, or a dark band on a light page.

Accent only in this block

The variables go on the container itself.

Dark only in this block

.dark on a container, with its own background.

Paid Pending New

On a container, redeclare the derived tokens

--tuc-accent-soft, --tuc-accent-ring and the tones' -soft tokens are computed from another token where they were declared, in :root — and are inherited by children already computed. At the root, changing --tuc-accent recomputes everything; on a container, the soft background would stay in the old color. That's why both blocks above repeat the calculation along with it.

.panel-green {
  --tuc-accent: #0f766e;
  --tuc-accent-hover: #115e59;
  --tuc-accent-fg: #ffffff;
  --tuc-accent-text: #0f766e;
  --tuc-accent-soft: color-mix(in oklab, var(--tuc-accent) 12%, transparent);
  --tuc-accent-ring: color-mix(in oklab, var(--tuc-accent) 35%, transparent);
}
.dark .panel-green {
  --tuc-accent-text: #2dd4bf;     /* accent text readable on a dark background */
}

/* .dark on a container: the tones change, and their soft background has to be redone there */
.band.dark {
  background: var(--tuc-bg);
  --tuc-success-soft: color-mix(in oklab, var(--tuc-success) 12%, transparent);
  --tuc-warning-soft: color-mix(in oklab, var(--tuc-warning) 14%, transparent);
  --tuc-danger-soft: color-mix(in oklab, var(--tuc-danger) 12%, transparent);
  --tuc-info-soft: color-mix(in oklab, var(--tuc-info) 12%, transparent);
}

A panel that opens outside the container — calendar, select list, menu, toast — is inserted at the end of <body> and doesn't inherit its variables. For those, declare the theme in :root, or use appendTo where the component offers it.

Compact layout

Below 40rem (640px) the layout becomes compact. The same breakpoint applies to the CSS and to the date picker's JavaScript, and it's based on width — not on pointer: coarse —, because a laptop with a touchscreen shouldn't get phone behavior.

ChangesToWhy
--tuc-text1rem, everywhere you type or tap: fields, select, date picker, color picker, upload, editor, checkboxes, tabs, pagination and buttoniOS Safari zooms in when focusing any field under 16px, and the whole page jumps. Toast and tooltip are left out: they have no fields
--tuc-control-height2.75rem (44px)Comfortable touch target
--tuc-swatch2.5remKeeps up with the larger text on touch
Date pickerOn a narrow and touch screen, the field doesn't receive focus and the panel itself is the inputWithout focus the keyboard doesn't come up, which would cover the calendar. The touch condition is there so typing isn't turned off in a narrow desktop window

If your project switches layout at another breakpoint, the library's compact mode won't follow — it's fixed at 40rem. Your own fields should use the same 16px on phones for the same reason; it's shadcn's text-base md:text-sm pattern.