Cueit

new

For SaaS admin screens, in the style of a Mac app.

Cueit makes shadcn/ui look like a native Apple app: soft rounded corners, thin dividers, light shadows and calm motion. It suits settings pages, dashboards and admin tools.

21 primitives3 extras.ds-cueit

A whole screen

The foundation plus the three Cueit extras, composed into a dashboard.

Search

Dashboard

Rendering “Pricing page teardown”.

Videos finished

34

6 in the last 7 days

Runtime produced

1h 12m

Ready to publish

In progress

1

Rendering right now

Waiting on you

2

To brief or retry

Recent renders
Your latest four, newest first.
VideoStatusCreated
Why onboarding emails failFinished2h ago
Pricing page teardownRenderingJust now
Three hiring mistakesNeeds briefYesterday
Launch recap, week 12Failed2d ago
Creator
Renews on June 4.
Active
Credits140 / 200

Resets Jun 4. See where they went

01

Principles

01

One action colour

Purple owns every routine action. Orange is identity, and exactly one point of emphasis per view. Two orange things in a view means neither works.

02

Continuous corners

Four radii, every one a superellipse: 10px buttons and fields, 14px tiles, 18px cards, 24px sheets. Pills are reserved for badges and the search field.

03

Hairlines, not greys

Separators are black at 7% or 12%, never a grey ramp step. Depth is a wide, soft shadow or a hairline, never a heavy pair.

04

Type with tracking

Ten steps from Display to Caption. Tracking goes negative as size goes up and positive as it goes down, which is what makes it read as Apple rather than generic sans.

05

Press, do not flash

Hover steps a fill one stop darker over 140ms. Press shrinks to 97% over 80ms. Focus is a 3.5px halo at 26%, keyboard only, never removed.

06

Materials float, content does not

Sidebars, popovers and tooltips are translucent material with blur. Cards, tables and tiles stay fully opaque, always.

02

Installation

1

Add the system

pnpm dlx shadcn@latest add @satoriui/cueit

One command installs the shadcn/ui foundation it restyles (21 primitives), the stylesheet at styles/cueit.css and the extras under components/satori-ui/cueit/. Primitives you already have are left alone.

2

Import the stylesheet

app/globals.css
@import "tailwindcss";
@import "tw-animate-css";
@import "../styles/cueit.css";

The file carries its own @theme and @utility blocks, so it has to be imported where Tailwind can see it.

3

Scope it

app/layout.tsx
<html lang="en" className="ds-cueit">

Every token lives under .ds-cueit. On html it themes the whole app. On a subtree it themes that subtree, and anything that portals out of it (dialogs, menus, tooltips) needs the class too.

4

Load the fonts

app/layout.tsx
import { Hanken_Grotesk, JetBrains_Mono } from "next/font/google";
const sans = Hanken_Grotesk({ subsets: ["latin"], variable: "--font-hanken-grotesk" });
const mono = JetBrains_Mono({ subsets: ["latin"], variable: "--font-jetbrains-mono" });
<body className={`${sans.variable} ${mono.variable}`}>

Apple hardware gets SF Pro through the system stack; everyone else gets Hanken Grotesk. Any loader works as long as the two variables exist, or the families are available by name.

5

Teach cn() the type scale

lib/utils.ts
import { clsx, type ClassValue } from "clsx";
import { extendTailwindMerge } from "tailwind-merge";
const twMerge = extendTailwindMerge({
extend: {
classGroups: {
"font-size": [{ text: ["display", "title1", "title2", "title3", "headline", "body", "callout", "subhead", "footnote", "caption"] }],
},
},
});
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}

Out of the box, tailwind-merge reads text-display as a text colour and silently drops it from cn("text-display", "text-foreground"). The extras do not depend on this, but your own code will.

03

Foundations

Colour

Semantic roles, not a ramp. Product code reads these; the ramps underneath exist so the roles have somewhere to point.

Action

  • actionLinks, active glyphs, borders, the focus halo
  • action-fillThe paint a primary button takes; its label is always white
  • action-tintSelected rows, icon wells

Brand

  • brandIdentity and one emphasis per view
  • brand-tintA brand wash behind a label

Status

  • successCompletion. The switch is green, not purple
  • infoIn progress, informational
  • warningNeeds attention. A status, never the brand
  • dangerFailed, destructive

Surfaces

  • canvasThe page
  • surfaceCards and tiles
  • sunkenInset tracks, secondary fills
  • raisedSheets and popovers

Text

  • primaryBody and headings
  • secondaryDescriptions
  • tertiaryLabels, timestamps
  • quaternaryDisabled glyphs only

Type

Ten steps, each with its own tracking. Size, line height, tracking and weight travel together, so a step is one class.

The quick brown foxdisplay40 / 44 · -0.8 · 700
The quick brown foxtitle132 / 38 · -0.6 · 700
The quick brown foxtitle226 / 32 · -0.45 · 700
The quick brown foxtitle321 / 26 · -0.3 · 600
The quick brown foxheadline17 / 22 · -0.25 · 600
The quick brown foxbody15 / 22 · -0.1 · 400
The quick brown foxcallout14 / 20 · -0.05 · 400
The quick brown foxsubhead13 / 18 · 0 · 400
The quick brown foxfootnote12 / 16 · +0.05 · 500
The quick brown foxcaption11 / 14 · +0.2 · 400

Speaks

SF Pro / Hanken Grotesk

SF Pro on Apple hardware via -apple-system; Hanken Grotesk everywhere else.

Counts

SF Mono / JetBrains Mono

Uppercase at 11px with +0.9px tracking marks anything that is data about the content.

Shape and elevation

Every corner is a superellipse. Depth is one hairline or one wide, soft shadow, never a heavy pair.

Radii

chip6px
control8px
button10px
tile14px
card18px
sheet24px

Elevation

xs
sm
md
lg
xl
  • xsActive tab, segmented thumb
  • smCards, tiles
  • mdA hovered interactive card, the filled tile
  • lgPopovers, menus, tooltips
  • xlDialogs
04

Components

Every demo below is the stock shadcn/ui component. Nothing in components/ui was edited: the stylesheet does all of it. The Code tab shows exactly what you would write.

Buttons

One primary per view. Hover steps the fill one stop darker; press shrinks to 97%; focus is a halo.

Badges

Outline pills in dense lists so colour does not shout. Status tints come from the tint-* utilities.

DraftFinishedRenderingNeeds briefFailedSelected
SolidCategoryInterrupt4K · 60fps

Fields

10px continuous corners with a visible edge. Focus turns the edge purple; an error replaces the hint.

Brief the render

Tell it what the panel should argue.

Shown above the video on the share page.

Enter a full email address.

Selection controls

18px boxes and circles painted purple when on. The switch is Apple's 51×31 and green: completion, not action.

Export

Resolution

Notifications

Email me when a render finishes

Applies immediately.

Weekly digest

Every Monday morning.

Tabs and segmented control

The sunken track never animates. Tabs lift the active trigger; the segmented control slides a real thumb.

The whole render at a glance: duration, panels and status.

Cards and tiles

18px corners, a hairline and elev-sm. The filled StatTile is the one point of emphasis a row gets.

Videos finished

34

6 in the last 7 days

Runtime produced

1h 12m

Ready to publish

In progress

1

Rendering right now

Workspace
Where your team and brand kit live.
Studio · 4 members · 2 brand kits
Loading
Skeletons take the field radius on the sunken fill.

Table

Mono-label headers, tabular numbers, hairline rows, a quiet hover fill and a tint for the selected row.

VideoStatusLengthPanelsCreated
Why onboarding emails failFinished0:4862h ago
Pricing page teardownRendering1:128Just now
Three hiring mistakesNeeds brief0:36—Yesterday
Launch recap, week 12Failed—52d ago
What a good cold email doesFinished0:5473d ago

Dialog, menu and tooltip

Floating surfaces take thick material and elev-lg; the dialog is a 24px sheet. All scale in from 96%.

Palettes

A palette moves the action/brand pair and nothing else. Surfaces, neutrals, status hues and shape are the system.

SelectedBrand
The switch stays green: status hues never move with the palette.

Videos finished

34

Brand carries the one emphasis

05

Extras

The components this system adds on top of the foundation. They install to components/satori-ui/cueit/ and read the same tokens as everything else.

StatTile

A KPI tile. One filled tile per row is the single point of emphasis a view gets.

components/satori-ui/cueit/stat-tile.tsx
import * as React from "react";
import { cn } from "@/lib/utils";
/**
* A KPI tile.
*
* At most one filled tile per row: that is the single point of emphasis the
* brand colour is allowed per view, and two of them means neither works. The
* filled tile takes elev-md plus a 1px white inset highlight at its top edge
* (Apple's key-light trick) instead of a border.
*
* The label is uppercase mono because it is data *about* the number rather
* than the number itself. The caption explains the number and never repeats
* it: "Total active users" under 34, not "34 users".
*
* Inner elements join their classes with plain strings rather than `cn()`:
* an unconfigured tailwind-merge reads `text-title1` as a colour and drops it
* when `text-foreground` follows.
*/
export function StatTile({
label,
value,
caption,
icon,
filled = false,
className,
...props
}: React.ComponentProps<"div"> & {
label: string;
value: string;
caption?: string;
icon?: React.ReactNode;
filled?: boolean;
}) {
return (
<div
data-slot="stat-tile"
data-filled={filled || undefined}
className={cn(
"flex flex-col gap-3 rounded-card squircle p-5",
filled
? "keylight bg-brand text-on-brand"
: "border border-separator bg-surface shadow-(--ds-elev-sm)",
className,
)}
{...props}
>
<div className="flex items-start justify-between gap-3">
<p className={`mono-label ${filled ? "text-on-brand/70" : "text-tertiary"}`}>{label}</p>
{icon && (
<span
aria-hidden="true"
className={`flex size-8 shrink-0 items-center justify-center rounded-control squircle [&_svg]:size-[18px] ${
filled ? "bg-on-brand/12 text-on-brand" : "bg-action-tint text-action"
}`}
>
{icon}
</span>
)}
</div>
<div>
<p className={`text-title1 tabular ${filled ? "text-on-brand" : "text-foreground"}`}>
{value}
</p>
{caption && (
<p className={`mt-1 text-subhead ${filled ? "text-on-brand/75" : "text-muted-foreground"}`}>
{caption}
</p>
)}
</div>
</div>
);
}

SegmentedControl

Apple's segmented control: the thumb slides, the track never animates.

components/satori-ui/cueit/segmented-control.tsx
"use client";
import * as React from "react";
import { cn } from "@/lib/utils";
/**
* Switching between views of the same content. Two to four options.
*
* Apple's segmented control: the thumb slides and the track never animates.
* The thumb is a real positioned element rather than a per-option background,
* which is the only way to get the slide. A background that cross-fades
* between two segments reads as two separate things blinking.
*
* For more than four options, or options that need a description, use Select
* or RadioGroup instead.
*/
export function SegmentedControl<T extends string>({
options,
value,
onChange,
size = "md",
className,
"aria-label": ariaLabel,
}: {
options: { value: T; label: string; icon?: React.ReactNode }[];
value: T;
onChange: (value: T) => void;
size?: "sm" | "md";
className?: string;
"aria-label": string;
}) {
const index = Math.max(
0,
options.findIndex((option) => option.value === value),
);
return (
<div
role="radiogroup"
aria-label={ariaLabel}
className={cn(
"relative isolate inline-grid w-fit rounded-control squircle bg-sunken p-0.5",
size === "sm" ? "h-7" : "h-9",
className,
)}
style={{ gridTemplateColumns: `repeat(${options.length}, minmax(0, 1fr))` }}
>
{/* The thumb: sized to one column and translated by whole columns, so
it stays correct whatever the container width turns out to be. */}
<span
aria-hidden="true"
className="pointer-events-none absolute inset-y-0.5 left-0.5 -z-10 rounded-[6px] squircle bg-raised shadow-(--ds-elev-xs) transition-transform duration-(--ds-dur-layout) ease-(--ds-ease)"
style={{
width: `calc((100% - 4px) / ${options.length})`,
transform: `translateX(${index * 100}%)`,
}}
/>
{options.map((option) => {
const selected = option.value === value;
return (
<button
key={option.value}
type="button"
role="radio"
aria-checked={selected}
onClick={() => onChange(option.value)}
// Plain string, not cn(): an unconfigured tailwind-merge reads
// `text-subhead` as a colour and drops it next to `text-foreground`.
className={[
"z-10 inline-flex items-center justify-center gap-1.5 rounded-[6px] px-3 font-medium outline-none",
"transition-colors duration-(--ds-dur-hover) ease-(--ds-ease)",
"focus-visible:shadow-(--ds-focus-ring)",
"[&_svg]:size-4 [&_svg]:shrink-0",
size === "sm" ? "text-footnote" : "text-subhead",
selected ? "text-foreground" : "text-muted-foreground hover:text-foreground",
].join(" ")}
>
{option.icon}
{option.label}
</button>
);
})}
</div>
);
}

PageHeader

Every screen opens with a title, a subtitle and its actions on the same row.

components/satori-ui/cueit/page-header.tsx
import type { ReactNode } from "react";
import { cn } from "@/lib/utils";
/**
* Every screen opens with a title and a subtitle.
*
* Page-level actions sit on the title row, never in the top bar: an action
* belongs next to the thing it acts on, and the top bar belongs to the app
* rather than to the page currently in it. Titles are sentence case.
*/
export function PageHeader({
title,
subtitle,
actions,
className,
}: {
title: string;
subtitle?: string;
actions?: ReactNode;
className?: string;
}) {
return (
<div
className={cn("mb-6 flex flex-wrap items-end justify-between gap-x-6 gap-y-3", className)}
>
<div className="min-w-0">
<h1 className="text-title2 text-foreground">{title}</h1>
{subtitle && <p className="mt-1 max-w-[68ch] text-body text-muted-foreground">{subtitle}</p>}
</div>
{actions && <div className="flex shrink-0 items-center gap-2">{actions}</div>}
</div>
);
}
06

Stylesheet

The whole system is this one file. Read it top to bottom: the vocabulary, the dark theme, the palettes, the Tailwind theme, the utilities, and then the cosmetic layer over each primitive.

styles/cueit.css
/* =========================================================================
CUEIT — a Satori UI design system
=========================================================================
An Apple-inspired system: continuous corners, a ten-step type scale with
per-size tracking, hairline separators, near-invisible elevation,
translucent chrome and motion that leaves fast and settles slowly.
HOW IT IS LAYERED
The foundation is plain shadcn/ui. Nothing in components/ui is edited.
This file does three things on top of it:
1. Defines the Cueit vocabulary as `--ds-*` custom properties, scoped
to `.ds-cueit` so several systems can live on one page.
2. Bridges that vocabulary onto the shadcn tokens (`--primary`,
`--border`, `--radius` …) so every primitive re-skins untouched.
3. Restyles the few details tokens cannot express — squircle corners,
press-to-shrink, the focus halo, the switch — by `data-slot`.
SCOPE
Put `ds-cueit` on <html> to theme a whole app. To theme a subtree, put
it on the subtree AND on anything that portals out of it
(DialogContent, SelectContent, DropdownMenuContent, TooltipContent):
portals render on <body>, outside the scope.
Every scope block is also written as `html.ds-cueit`. This file has to
be imported at the top of globals.css, so the project's own `:root` and
`.dark` token blocks come after it; the extra type selector is what lets
the system win on <html> instead of losing on source order.
WHAT THE SKIN OWNS
The cosmetic layer (section 07) sets colour, border, radius, shadow,
type and motion on each primitive. It deliberately leaves layout alone
(padding, gap, width), so a className like `gap-0 pt-0` on a Card still
works.
FONTS
SF Pro on Apple hardware via -apple-system; Hanken Grotesk everywhere
else, JetBrains Mono for data. Load them however you like and expose
them as --font-hanken-grotesk / --font-jetbrains-mono (next/font does
this), or just make "Hanken Grotesk" and "JetBrains Mono" available.
========================================================================= */
/* -------------------------------------------------------------------------
01 VOCABULARY — LIGHT
Purple owns every action. Orange owns identity plus exactly one point of
emphasis per view. Never invent a colour outside these.
------------------------------------------------------------------------- */
html.ds-cueit,
.ds-cueit {
/* Fonts */
--ds-font-sans:
-apple-system, BlinkMacSystemFont, var(--font-hanken-grotesk, "Hanken Grotesk"),
"Segoe UI", Roboto, Helvetica, Arial, sans-serif;
--ds-font-mono:
ui-monospace, SFMono-Regular, var(--font-jetbrains-mono, "JetBrains Mono"),
Menlo, Consolas, monospace;
/* Ramps that the semantic layer points at */
--ds-purple-500: #7c63c6;
--ds-purple-600: #674dad;
--ds-orange-500: #e08a3e;
--ds-orange-600: #c77321;
--ds-gray-400: #8f9298;
--ds-gray-500: #686b72;
--ds-gray-600: #565960;
--ds-gray-900: #21242a;
/* Action pair. --ds-accent is the TYPE colour (links, glyphs, borders,
the halo) and lightens in dark mode. --ds-accent-fill is the PAINT a
button takes and never moves, so its label can always be white. */
--ds-accent: var(--ds-purple-500);
--ds-accent-fill: var(--ds-purple-500);
--ds-accent-fill-hover: #6c53b4;
--ds-accent-fill-active: #5f44a4;
--ds-accent-on-fill: #ffffff;
--ds-accent-tint: #efecff;
--ds-accent-on-tint: #735abc;
/* Identity */
--ds-brand: var(--ds-orange-500);
--ds-brand-hover: var(--ds-orange-600);
--ds-brand-tint: #fff0e6;
--ds-brand-on-tint: #a65a05;
/* Orange is too light to carry a white label; its fill takes ink. */
--ds-brand-contrast: var(--ds-gray-900);
/* Roles */
--ds-success: #34c759;
--ds-success-fill: #22a344;
--ds-success-tint: #e6fae7;
--ds-success-on-tint: #04802f;
--ds-info: #007aff;
--ds-info-fill: #0265d5;
--ds-info-tint: #edf4fe;
--ds-info-on-tint: #016ade;
--ds-warning: #e08a3e;
--ds-warning-tint: #fff0e6;
--ds-warning-on-tint: #a65a05;
--ds-danger: #ff3b30;
--ds-danger-fill: #e30f10;
--ds-danger-tint: #fef0ee;
--ds-danger-on-tint: #dd050a;
--ds-danger-contrast: #ffffff;
/* Badge outlines for the tint-* utilities: each hue at 35%. Every Satori UI
system defines tint-* identically and sets these per system. */
--ds-success-edge: color-mix(in srgb, var(--ds-success) 35%, transparent);
--ds-info-edge: color-mix(in srgb, var(--ds-info) 35%, transparent);
--ds-warning-edge: color-mix(in srgb, var(--ds-warning) 35%, transparent);
--ds-danger-edge: color-mix(in srgb, var(--ds-danger) 35%, transparent);
--ds-action-edge: color-mix(in srgb, var(--ds-accent) 35%, transparent);
--ds-brand-edge: color-mix(in srgb, var(--ds-brand) 35%, transparent);
/* Surfaces: flat canvas, flat white cards. No gradients anywhere. */
--ds-bg-canvas: #f5f5f7;
--ds-bg-surface: #ffffff;
--ds-bg-sunken: #efeff2;
--ds-bg-raised: #ffffff;
/* Text at three levels; quaternary is disabled glyphs only. */
--ds-text-primary: var(--ds-gray-900);
--ds-text-secondary: var(--ds-gray-600);
--ds-text-tertiary: var(--ds-gray-500);
--ds-text-quaternary: var(--ds-gray-400);
/* Separators are hairlines, never a grey. */
--ds-separator: rgba(0, 0, 0, 0.07);
--ds-separator-strong: rgba(0, 0, 0, 0.12);
/* Fill steps for hover and inset controls */
--ds-fill-quiet: rgba(0, 0, 0, 0.035);
--ds-fill-hover: rgba(0, 0, 0, 0.055);
--ds-fill-active: rgba(0, 0, 0, 0.085);
/* Materials belong to chrome that floats over content, never to content. */
--ds-material: rgba(255, 255, 255, 0.76);
--ds-material-thick: rgba(255, 255, 255, 0.92);
--ds-material-blur: blur(20px) saturate(180%);
--ds-scrim: rgba(0, 0, 0, 0.38);
/* Sidebar selection: a wash of the accent with a hairline of the same hue */
--ds-nav-selected: color-mix(in srgb, var(--ds-accent) 12%, var(--ds-bg-surface));
--ds-nav-selected-border: color-mix(in srgb, var(--ds-accent) 30%, transparent);
/* Elevation — wide, soft, nearly invisible. One border or one shadow. */
--ds-elev-xs: 0 1px 1px rgba(16, 18, 24, 0.04);
--ds-elev-sm: 0 1px 2px rgba(16, 18, 24, 0.04), 0 4px 12px rgba(16, 18, 24, 0.05);
--ds-elev-md: 0 2px 4px rgba(16, 18, 24, 0.05), 0 8px 24px rgba(16, 18, 24, 0.07);
--ds-elev-lg: 0 4px 8px rgba(16, 18, 24, 0.06), 0 16px 40px rgba(16, 18, 24, 0.1);
--ds-elev-xl: 0 8px 16px rgba(16, 18, 24, 0.08), 0 32px 72px rgba(16, 18, 24, 0.16);
--ds-key-light: inset 0 1px 0 rgba(255, 255, 255, 0.18);
/* Focus is a 3.5px halo at 26% and no outline. */
--ds-focus-ring: 0 0 0 3.5px color-mix(in srgb, var(--ds-accent) 26%, transparent);
--ds-focus-ring-danger: 0 0 0 3.5px color-mix(in srgb, var(--ds-danger) 26%, transparent);
/* Shape — Apple's corners are a superellipse. Components set both a
radius and `corner-shape: squircle`; browsers without it fall back
to an arc that still looks right. */
--ds-radius-chip: 6px;
--ds-radius-control: 8px;
--ds-radius-button: 10px;
--ds-radius-field: 10px;
--ds-radius-tile: 14px;
--ds-radius-card: 18px;
--ds-radius-sheet: 24px;
--ds-radius-pill: 999px;
/* Motion — fast out of the gate, long settle. The spring is for the
switch thumb only. */
--ds-ease: cubic-bezier(0.32, 0.72, 0, 1);
--ds-ease-spring: cubic-bezier(0.34, 1.28, 0.64, 1);
--ds-dur-hover: 140ms;
--ds-dur-press: 80ms;
--ds-dur-layout: 220ms;
--ds-dur-progress: 340ms;
--ds-dur-sheet: 480ms;
/* ---- The shadcn bridge -------------------------------------------------
Every token the foundation reads, mapped onto the vocabulary above.
This is what re-skins components/ui without editing a single file. */
--background: var(--ds-bg-canvas);
--foreground: var(--ds-text-primary);
--card: var(--ds-bg-surface);
--card-foreground: var(--ds-text-primary);
--popover: var(--ds-bg-raised);
--popover-foreground: var(--ds-text-primary);
--primary: var(--ds-accent-fill);
--primary-foreground: var(--ds-accent-on-fill);
--secondary: var(--ds-bg-sunken);
--secondary-foreground: var(--ds-text-primary);
--muted: var(--ds-bg-sunken);
--muted-foreground: var(--ds-text-secondary);
/* shadcn's --accent is its hover fill, not a brand colour. */
--accent: var(--ds-fill-quiet);
--accent-foreground: var(--ds-text-primary);
--destructive: var(--ds-danger-fill);
--destructive-foreground: var(--ds-danger-contrast);
--border: var(--ds-separator);
--input: var(--ds-separator-strong);
--ring: var(--ds-accent);
--radius: 10px;
--chart-1: var(--ds-accent);
--chart-2: var(--ds-brand);
--chart-3: var(--ds-info);
--chart-4: var(--ds-success);
--chart-5: var(--ds-text-tertiary);
--sidebar: var(--ds-material);
--sidebar-foreground: var(--ds-text-primary);
--sidebar-primary: var(--ds-accent-fill);
--sidebar-primary-foreground: var(--ds-accent-on-fill);
--sidebar-accent: var(--ds-fill-quiet);
--sidebar-accent-foreground: var(--ds-text-primary);
--sidebar-border: var(--ds-separator);
--sidebar-ring: var(--ds-accent);
/* ---- Base typography on the scope ---------------------------------- */
font-family: var(--ds-font-sans);
font-size: 15px;
line-height: 22px;
letter-spacing: -0.1px;
color: var(--ds-text-primary);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
font-synthesis-weight: none;
}
/* -------------------------------------------------------------------------
02 VOCABULARY — DARK
Dark raises both lightness and chroma. Surfaces are near-black but never
pure; shadows get stronger, not weaker. Honours the host's `.dark`.
------------------------------------------------------------------------- */
html.ds-cueit:where(.dark, .dark *):not(:where(.dark .light, .dark .light *)),
.ds-cueit:where(.dark, .dark *):not(:where(.dark .light, .dark .light *)) {
--ds-accent: #a08ada;
--ds-accent-fill: var(--ds-purple-500);
--ds-accent-fill-hover: #6c53b4;
--ds-accent-fill-active: #5f44a4;
--ds-accent-on-fill: #ffffff;
--ds-accent-tint: #2c2540;
--ds-accent-on-tint: #9b85d4;
--ds-brand: #f0a45c;
--ds-brand-hover: #ffba7b;
--ds-brand-tint: #3c240b;
--ds-brand-on-tint: #ca8137;
--ds-brand-contrast: #0d0d0f;
--ds-success: #30d158;
--ds-success-fill: #30d158;
--ds-success-tint: #02340f;
--ds-success-on-tint: #08ab3f;
--ds-info: #0a84ff;
--ds-info-fill: #0a84ff;
--ds-info-tint: #052953;
--ds-info-on-tint: #3992ff;
--ds-warning: #f0a45c;
--ds-warning-tint: #3c240b;
--ds-warning-on-tint: #ca8137;
--ds-danger: #ff453a;
--ds-danger-fill: #ff453a;
--ds-danger-tint: #4f100c;
--ds-danger-on-tint: #ff5145;
--ds-danger-contrast: #0d0d0f;
--ds-bg-canvas: #0d0d0f;
--ds-bg-surface: #161619;
--ds-bg-sunken: #0d0d0f;
--ds-bg-raised: #1e1e22;
--ds-text-primary: #f2f4f6;
--ds-text-secondary: #a6a9ae;
--ds-text-tertiary: #888c92;
--ds-text-quaternary: #686b72;
--ds-separator: rgba(255, 255, 255, 0.09);
--ds-separator-strong: rgba(255, 255, 255, 0.16);
--ds-fill-quiet: rgba(255, 255, 255, 0.05);
--ds-fill-hover: rgba(255, 255, 255, 0.08);
--ds-fill-active: rgba(255, 255, 255, 0.12);
--ds-material: rgba(22, 22, 25, 0.72);
--ds-material-thick: rgba(22, 22, 25, 0.92);
--ds-scrim: rgba(0, 0, 0, 0.55);
--ds-nav-selected: color-mix(in srgb, var(--ds-accent) 22%, var(--ds-bg-raised));
--ds-nav-selected-border: color-mix(in srgb, var(--ds-accent) 34%, transparent);
--ds-elev-xs: 0 1px 1px rgba(0, 0, 0, 0.3);
--ds-elev-sm: 0 1px 2px rgba(0, 0, 0, 0.32), 0 4px 12px rgba(0, 0, 0, 0.36);
--ds-elev-md: 0 2px 4px rgba(0, 0, 0, 0.36), 0 8px 24px rgba(0, 0, 0, 0.44);
--ds-elev-lg: 0 4px 8px rgba(0, 0, 0, 0.4), 0 16px 40px rgba(0, 0, 0, 0.52);
--ds-elev-xl: 0 8px 16px rgba(0, 0, 0, 0.46), 0 32px 72px rgba(0, 0, 0, 0.62);
--ds-key-light: inset 0 1px 0 rgba(255, 255, 255, 0.08);
}
/* -------------------------------------------------------------------------
03 PALETTES (data-palette="cobalt" | "beacon")
The action/brand pair is the only thing a palette may move. Surfaces,
neutrals, status hues, type and shape are the system and never change.
------------------------------------------------------------------------- */
html.ds-cueit[data-palette="cobalt"],
.ds-cueit[data-palette="cobalt"] {
--ds-accent: #2563eb;
--ds-accent-fill: #2563eb;
--ds-accent-fill-hover: #1451d8;
--ds-accent-fill-active: #0240c7;
--ds-accent-tint: #eef4ff;
--ds-accent-on-tint: #0037b4;
--ds-brand: #22c55e;
--ds-brand-hover: #09b251;
--ds-brand-tint: #e5fae8;
--ds-brand-on-tint: #058139;
--ds-brand-contrast: var(--ds-gray-900);
}
html.ds-cueit[data-palette="cobalt"]:where(.dark, .dark *):not(:where(.dark .light, .dark .light *)),
.ds-cueit[data-palette="cobalt"]:where(.dark, .dark *):not(:where(.dark .light, .dark .light *)) {
--ds-accent: #8db4ff;
--ds-accent-fill: #2563eb;
--ds-accent-tint: #102756;
--ds-accent-on-tint: #8db4ff;
--ds-brand: #5fd07c;
--ds-brand-hover: #7fdc96;
--ds-brand-tint: #013413;
--ds-brand-on-tint: #5fd07c;
--ds-brand-contrast: #0d0d0f;
}
html.ds-cueit[data-palette="beacon"],
.ds-cueit[data-palette="beacon"] {
--ds-accent: #2c68f6;
--ds-accent-fill: #2a64ec;
--ds-accent-fill-hover: #2557cf;
--ds-accent-fill-active: #214db6;
--ds-accent-tint: #f0f4fe;
--ds-accent-on-tint: #1f49ac;
--ds-brand: #12a9f5;
--ds-brand-hover: #119be1;
--ds-brand-tint: #eef9fe;
--ds-brand-on-tint: #0b6998;
--ds-brand-contrast: var(--ds-gray-900);
}
html.ds-cueit[data-palette="beacon"]:where(.dark, .dark *):not(:where(.dark .light, .dark .light *)),
.ds-cueit[data-palette="beacon"]:where(.dark, .dark *):not(:where(.dark .light, .dark .light *)) {
--ds-accent: #a0bbfb;
--ds-accent-fill: #2a64ec;
--ds-accent-tint: #0f2354;
--ds-accent-on-tint: #a0bbfb;
--ds-brand: #65c7f9;
--ds-brand-hover: #8ad6fb;
--ds-brand-tint: #05334a;
--ds-brand-on-tint: #41baf7;
--ds-brand-contrast: #0d0d0f;
}
/* -------------------------------------------------------------------------
04 TAILWIND THEME
Utilities for product code: `text-action`, `bg-brand-tint`,
`rounded-card`, `text-title2` … Every value points at a `--ds-*`
variable, so the same class names can serve a second design system.
------------------------------------------------------------------------- */
@theme inline {
/* Type scale: ten steps, each with its own tracking. Tracking goes
negative as size goes up and positive as it goes down. */
--text-display: 40px;
--text-display--line-height: 44px;
--text-display--letter-spacing: -0.8px;
--text-display--font-weight: 700;
--text-title1: 32px;
--text-title1--line-height: 38px;
--text-title1--letter-spacing: -0.6px;
--text-title1--font-weight: 700;
--text-title2: 26px;
--text-title2--line-height: 32px;
--text-title2--letter-spacing: -0.45px;
--text-title2--font-weight: 700;
--text-title3: 21px;
--text-title3--line-height: 26px;
--text-title3--letter-spacing: -0.3px;
--text-title3--font-weight: 600;
--text-headline: 17px;
--text-headline--line-height: 22px;
--text-headline--letter-spacing: -0.25px;
--text-headline--font-weight: 600;
--text-body: 15px;
--text-body--line-height: 22px;
--text-body--letter-spacing: -0.1px;
--text-body--font-weight: 400;
--text-callout: 14px;
--text-callout--line-height: 20px;
--text-callout--letter-spacing: -0.05px;
--text-callout--font-weight: 400;
--text-subhead: 13px;
--text-subhead--line-height: 18px;
--text-subhead--letter-spacing: 0px;
--text-subhead--font-weight: 400;
--text-footnote: 12px;
--text-footnote--line-height: 16px;
--text-footnote--letter-spacing: 0.05px;
--text-footnote--font-weight: 500;
--text-caption: 11px;
--text-caption--line-height: 14px;
--text-caption--letter-spacing: 0.2px;
--text-caption--font-weight: 400;
/* Action: text-action / border-action take the type colour;
bg-action-fill takes the paint. Keeping them apart keeps labels white. */
--color-action: var(--ds-accent);
--color-action-fill: var(--ds-accent-fill);
--color-action-fill-hover: var(--ds-accent-fill-hover);
--color-action-fill-active: var(--ds-accent-fill-active);
--color-action-tint: var(--ds-accent-tint);
--color-on-action: var(--ds-accent-on-fill);
--color-on-action-tint: var(--ds-accent-on-tint);
--color-brand: var(--ds-brand);
--color-brand-hover: var(--ds-brand-hover);
--color-brand-tint: var(--ds-brand-tint);
--color-on-brand: var(--ds-brand-contrast);
--color-on-brand-tint: var(--ds-brand-on-tint);
--color-success: var(--ds-success);
--color-success-fill: var(--ds-success-fill);
--color-success-tint: var(--ds-success-tint);
--color-on-success-tint: var(--ds-success-on-tint);
--color-info: var(--ds-info);
--color-info-fill: var(--ds-info-fill);
--color-info-tint: var(--ds-info-tint);
--color-on-info-tint: var(--ds-info-on-tint);
--color-warning: var(--ds-warning);
--color-warning-tint: var(--ds-warning-tint);
--color-on-warning-tint: var(--ds-warning-on-tint);
--color-danger: var(--ds-danger);
--color-danger-fill: var(--ds-danger-fill);
--color-danger-tint: var(--ds-danger-tint);
--color-on-danger: var(--ds-danger-contrast);
--color-on-danger-tint: var(--ds-danger-on-tint);
--color-canvas: var(--ds-bg-canvas);
--color-surface: var(--ds-bg-surface);
--color-sunken: var(--ds-bg-sunken);
--color-raised: var(--ds-bg-raised);
--color-tertiary: var(--ds-text-tertiary);
--color-quaternary: var(--ds-text-quaternary);
--color-separator: var(--ds-separator);
--color-separator-strong: var(--ds-separator-strong);
--color-fill-quiet: var(--ds-fill-quiet);
--color-fill-hover: var(--ds-fill-hover);
--color-fill-active: var(--ds-fill-active);
--color-nav-selected: var(--ds-nav-selected);
--color-nav-selected-border: var(--ds-nav-selected-border);
--radius-chip: var(--ds-radius-chip);
--radius-control: var(--ds-radius-control);
--radius-button: var(--ds-radius-button);
--radius-field: var(--ds-radius-field);
--radius-tile: var(--ds-radius-tile);
--radius-card: var(--ds-radius-card);
--radius-sheet: var(--ds-radius-sheet);
--radius-pill: var(--ds-radius-pill);
--ease-standard: var(--ds-ease);
--ease-spring: var(--ds-ease-spring);
}
/* -------------------------------------------------------------------------
05 UTILITIES
------------------------------------------------------------------------- */
/* THE MONO MOTIF IS LOAD-BEARING. Uppercase mono at 11px marks anything
that is data ABOUT the content: KPI labels, table headers, timestamps,
IDs, amounts. Never prose, buttons or headings. */
@utility mono-label {
font-family: var(--ds-font-mono);
font-size: 11px;
line-height: 14px;
letter-spacing: 0.9px;
font-weight: 500;
text-transform: uppercase;
font-variant-numeric: tabular-nums;
}
/* The same motif at natural case, for values rather than labels. */
@utility mono-value {
font-family: var(--ds-font-mono);
font-size: 11px;
line-height: 14px;
letter-spacing: 0.2px;
font-weight: 500;
font-variant-numeric: tabular-nums;
}
@utility tabular {
font-variant-numeric: tabular-nums;
}
/* Continuous corners. Applied per component, never blanket — it would
turn circular avatars into squircles. */
@utility squircle {
corner-shape: squircle;
}
/* Translucency belongs to chrome that floats over content, never content. */
@utility material {
background-color: var(--ds-material);
backdrop-filter: var(--ds-material-blur);
-webkit-backdrop-filter: var(--ds-material-blur);
}
@utility material-thick {
background-color: var(--ds-material-thick);
backdrop-filter: var(--ds-material-blur);
-webkit-backdrop-filter: var(--ds-material-blur);
}
/* Press-to-shrink: scale(0.97) over 80ms, never a colour flash. */
@utility press {
transition: transform var(--ds-dur-press) var(--ds-ease);
&:active:not(:disabled):not([aria-disabled="true"]) {
transform: scale(0.97);
}
}
/* A filled surface takes a shadow plus a 1px white inset highlight at its
top edge — Apple's key-light trick — instead of a border. */
@utility keylight {
box-shadow: var(--ds-key-light), var(--ds-elev-md);
}
@utility focus-halo {
outline: none;
box-shadow: var(--ds-focus-ring);
}
/* Status tints for the shadcn Badge: `<Badge variant="outline"
className="tint-success">`. The text colour is solved against its own
background, which is what keeps a pale pill readable. Every Satori UI
system defines these identically and only sets the --ds-* values, so
two systems can share a build and markup moves between them. The state is
always named in words — colour is the second signal, never the only one. */
@utility tint-success {
background-color: var(--ds-success-tint);
color: var(--ds-success-on-tint);
border-color: var(--ds-success-edge);
}
@utility tint-info {
background-color: var(--ds-info-tint);
color: var(--ds-info-on-tint);
border-color: var(--ds-info-edge);
}
@utility tint-warning {
background-color: var(--ds-warning-tint);
color: var(--ds-warning-on-tint);
border-color: var(--ds-warning-edge);
}
@utility tint-danger {
background-color: var(--ds-danger-tint);
color: var(--ds-danger-on-tint);
border-color: var(--ds-danger-edge);
}
@utility tint-action {
background-color: var(--ds-accent-tint);
color: var(--ds-accent-on-tint);
border-color: var(--ds-action-edge);
}
@utility tint-brand {
background-color: var(--ds-brand-tint);
color: var(--ds-brand-on-tint);
border-color: var(--ds-brand-edge);
}
/* -------------------------------------------------------------------------
06 BASE BEHAVIOUR INSIDE THE SCOPE
------------------------------------------------------------------------- */
.ds-cueit :is(code, kbd, pre, samp, .font-mono) {
font-family: var(--ds-font-mono);
}
.ds-cueit ::selection {
background-color: color-mix(in srgb, var(--ds-accent) 24%, transparent);
}
/* Focus is a halo, keyboard only, no outline. */
:where(.ds-cueit, .ds-cueit *):focus-visible {
outline: none;
box-shadow: var(--ds-focus-ring);
}
/* Disabled is opacity .4 with no colour change. */
:where(.ds-cueit, .ds-cueit *):disabled,
:where(.ds-cueit, .ds-cueit *)[aria-disabled="true"],
:where(.ds-cueit, .ds-cueit *)[data-disabled] {
opacity: 0.4;
}
/* Lucide at 1.75 stroke with round caps — the nearest open match for SF
Symbols' optical weight. */
.ds-cueit svg.lucide {
stroke-width: 1.75;
stroke-linecap: round;
stroke-linejoin: round;
}
.ds-cueit :is(input, textarea)::placeholder {
color: var(--ds-text-tertiary);
}
.ds-cueit table {
font-variant-numeric: tabular-nums;
}
/* -------------------------------------------------------------------------
07 COSMETIC LAYER — the shadcn primitives, by data-slot
These rules are unlayered on purpose: they have to beat the utility
classes the primitives already carry. That also means they beat a
className you pass, so they stick to skin (colour, border, radius,
shadow, type, motion) and never set padding, gap or width.
Tailwind v4 moves things with the `translate` property, not
`transform`. Anything here that moves an element sets `translate`, or
it would stack on top of the primitive's own offset.
------------------------------------------------------------------------- */
/* Button: 10px continuous corners; hover steps the fill one stop darker
over 140ms; press shrinks to 97% over 80ms. Never a lift, never a fade. */
[data-slot="button"]:where(.ds-cueit, .ds-cueit *) {
border-radius: var(--ds-radius-button);
corner-shape: squircle;
font-weight: 500;
box-shadow: none;
transition:
background-color var(--ds-dur-hover) var(--ds-ease),
border-color var(--ds-dur-hover) var(--ds-ease),
color var(--ds-dur-hover) var(--ds-ease),
transform var(--ds-dur-press) var(--ds-ease);
}
[data-slot="button"]:where(.ds-cueit, .ds-cueit *):active:not(:disabled) {
transform: scale(0.97);
}
[data-slot="button"][data-variant="default"]:where(.ds-cueit, .ds-cueit *):hover {
background-color: var(--ds-accent-fill-hover);
}
[data-slot="button"][data-variant="default"]:where(.ds-cueit, .ds-cueit *):active {
background-color: var(--ds-accent-fill-active);
}
[data-slot="button"][data-variant="outline"]:where(.ds-cueit, .ds-cueit *) {
background-color: var(--ds-bg-surface);
border-color: var(--ds-separator-strong);
}
[data-slot="button"][data-variant="outline"]:where(.ds-cueit, .ds-cueit *):hover {
background-color: var(--ds-fill-quiet);
}
[data-slot="button"][data-variant="secondary"]:where(.ds-cueit, .ds-cueit *):hover {
background-color: var(--ds-fill-hover);
}
[data-slot="button"][data-variant="destructive"]:where(.ds-cueit, .ds-cueit *) {
background-color: var(--ds-danger-fill);
color: var(--ds-danger-contrast);
}
[data-slot="button"][data-variant="destructive"]:where(.ds-cueit, .ds-cueit *):hover {
filter: brightness(1.08);
}
[data-slot="button"][data-variant="destructive"]:where(.ds-cueit, .ds-cueit *):focus-visible {
box-shadow: var(--ds-focus-ring-danger);
}
[data-slot="button"][data-variant="link"]:where(.ds-cueit, .ds-cueit *) {
color: var(--ds-accent);
}
[data-slot="button"][data-variant="link"]:where(.ds-cueit, .ds-cueit *):active {
transform: none;
}
/* Badge: shadcn's pill at 12/16 medium is already Cueit's footnote step, so
only colour is set. Status hues come from a tint-* utility so the state
can be read in words and hue together. */
[data-slot="badge"][data-variant="outline"]:where(.ds-cueit, .ds-cueit *):not([class*="tint-"]) {
border-color: var(--ds-separator-strong);
color: var(--ds-text-secondary);
}
[data-slot="badge"][data-variant="destructive"]:where(.ds-cueit, .ds-cueit *) {
background-color: var(--ds-danger-fill);
color: var(--ds-danger-contrast);
}
/* Card: 18px continuous corners, a hairline plus elev-sm — the heaviest
treatment in the system. Spacing stays shadcn's so classes still work. */
[data-slot="card"]:where(.ds-cueit, .ds-cueit *) {
border-radius: var(--ds-radius-card);
corner-shape: squircle;
border-color: var(--ds-separator);
box-shadow: var(--ds-elev-sm);
}
[data-slot="card-title"]:where(.ds-cueit, .ds-cueit *) {
font-size: 21px;
line-height: 26px;
letter-spacing: -0.3px;
font-weight: 600;
}
[data-slot="card-description"]:where(.ds-cueit, .ds-cueit *) {
font-size: 13px;
line-height: 18px;
letter-spacing: 0;
}
/* Fields: 10px corners, a visible edge, white surface. Focus turns the edge
purple and brings the halo up in one 140ms step. Error replaces the hint. */
:is([data-slot="input"], [data-slot="textarea"], [data-slot="select-trigger"]):where(.ds-cueit, .ds-cueit *) {
border-radius: var(--ds-radius-field);
corner-shape: squircle;
border-color: var(--ds-separator-strong);
background-color: var(--ds-bg-surface);
box-shadow: none;
font-size: 15px;
letter-spacing: -0.1px;
transition:
border-color var(--ds-dur-hover) var(--ds-ease),
box-shadow var(--ds-dur-hover) var(--ds-ease),
background-color var(--ds-dur-hover) var(--ds-ease);
}
:is([data-slot="input"], [data-slot="textarea"], [data-slot="select-trigger"]):where(.ds-cueit, .ds-cueit *):focus-visible {
border-color: var(--ds-accent);
box-shadow: var(--ds-focus-ring);
}
:is([data-slot="input"], [data-slot="textarea"], [data-slot="select-trigger"]):where(.ds-cueit, .ds-cueit *)[aria-invalid="true"] {
border-color: var(--ds-danger);
}
:is([data-slot="input"], [data-slot="textarea"], [data-slot="select-trigger"]):where(.ds-cueit, .ds-cueit *)[aria-invalid="true"]:focus-visible {
box-shadow: var(--ds-focus-ring-danger);
}
:is([data-slot="input"], [data-slot="textarea"]):where(.ds-cueit, .ds-cueit *):disabled {
background-color: var(--ds-bg-sunken);
}
[data-slot="select-trigger"][data-size="sm"]:where(.ds-cueit, .ds-cueit *) {
font-size: 13px;
height: 28px;
}
[data-slot="label"]:where(.ds-cueit, .ds-cueit *) {
font-size: 13px;
line-height: 18px;
font-weight: 500;
}
/* Selection controls: 18px, a 6px squircle for the box, a circle for the
radio, purple paint when on. */
[data-slot="checkbox"]:where(.ds-cueit, .ds-cueit *) {
width: 18px;
height: 18px;
border-radius: var(--ds-radius-chip);
corner-shape: squircle;
border-color: var(--ds-separator-strong);
background-color: var(--ds-bg-surface);
box-shadow: none;
transition:
background-color var(--ds-dur-hover) var(--ds-ease),
border-color var(--ds-dur-hover) var(--ds-ease),
box-shadow var(--ds-dur-hover) var(--ds-ease);
}
[data-slot="checkbox"]:where(.ds-cueit, .ds-cueit *):is([data-state="checked"], [data-state="indeterminate"]) {
border-color: var(--ds-accent-fill);
background-color: var(--ds-accent-fill);
color: var(--ds-accent-on-fill);
}
[data-slot="radio-group-item"]:where(.ds-cueit, .ds-cueit *) {
width: 18px;
height: 18px;
border-color: var(--ds-separator-strong);
background-color: var(--ds-bg-surface);
box-shadow: none;
transition:
background-color var(--ds-dur-hover) var(--ds-ease),
border-color var(--ds-dur-hover) var(--ds-ease);
}
[data-slot="radio-group-item"][data-state="checked"]:where(.ds-cueit, .ds-cueit *) {
border-color: var(--ds-accent-fill);
background-color: var(--ds-accent-fill);
}
[data-slot="radio-group-item"][data-state="checked"]:where(.ds-cueit, .ds-cueit *) svg {
fill: var(--ds-accent-on-fill);
color: var(--ds-accent-on-fill);
width: 6px;
height: 6px;
}
/* Switch: Apple's 51×31 proportions, green when on — this is completion,
not action, so it is one of the few controls that is not purple. The
thumb is the only springy thing in the system. */
[data-slot="switch"]:where(.ds-cueit, .ds-cueit *) {
height: 31px;
width: 51px;
padding: 2px;
border: 0;
box-shadow: none;
background-color: #d4d4d9;
transition: background-color var(--ds-dur-layout) var(--ds-ease);
}
[data-slot="switch"]:where(.ds-cueit, .ds-cueit *):where(.dark *):not(:where(.dark .light, .dark .light *)) {
background-color: #3a3a3f;
}
[data-slot="switch"][data-state="checked"]:where(.ds-cueit, .ds-cueit *) {
background-color: var(--ds-success-fill);
}
[data-slot="switch"][data-size="sm"]:where(.ds-cueit, .ds-cueit *) {
height: 24px;
width: 40px;
}
/* Travel is track width minus both 2px insets minus the thumb: 51-4-27 and
40-4-20. Set as `translate` so it replaces the primitive's own offset
rather than adding to it. */
[data-slot="switch-thumb"]:where(.ds-cueit, .ds-cueit *) {
width: 27px;
height: 27px;
background-color: #ffffff;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.14), 0 2px 6px rgba(0, 0, 0, 0.1);
translate: 0 0;
transition: translate var(--ds-dur-layout) var(--ds-ease-spring);
}
[data-slot="switch-thumb"][data-state="checked"]:where(.ds-cueit, .ds-cueit *) {
translate: 20px 0;
}
[data-slot="switch"][data-size="sm"] [data-slot="switch-thumb"]:where(.ds-cueit, .ds-cueit *) {
width: 20px;
height: 20px;
}
[data-slot="switch"][data-size="sm"] [data-slot="switch-thumb"][data-state="checked"]:where(.ds-cueit, .ds-cueit *) {
translate: 16px 0;
}
/* Tabs: the sunken track never animates; the active trigger lifts onto the
surface with elev-xs. */
[data-slot="tabs-list"][data-variant="default"]:where(.ds-cueit, .ds-cueit *) {
border-radius: var(--ds-radius-control);
corner-shape: squircle;
background-color: var(--ds-bg-sunken);
padding: 3px;
}
[data-slot="tabs-trigger"]:where(.ds-cueit, .ds-cueit *) {
border-radius: 6px;
corner-shape: squircle;
font-size: 13px;
font-weight: 500;
color: var(--ds-text-secondary);
transition:
color var(--ds-dur-hover) var(--ds-ease),
background-color var(--ds-dur-hover) var(--ds-ease);
}
[data-slot="tabs-trigger"]:where(.ds-cueit, .ds-cueit *):hover {
color: var(--ds-text-primary);
}
[data-slot="tabs-list"][data-variant="default"] [data-slot="tabs-trigger"][data-state="active"]:where(.ds-cueit, .ds-cueit *) {
background-color: var(--ds-bg-raised);
border-color: transparent;
color: var(--ds-text-primary);
box-shadow: var(--ds-elev-xs);
}
[data-slot="tabs-list"][data-variant="line"] [data-slot="tabs-trigger"][data-state="active"]:where(.ds-cueit, .ds-cueit *) {
color: var(--ds-text-primary);
}
[data-slot="tabs-list"][data-variant="line"] [data-slot="tabs-trigger"]:where(.ds-cueit, .ds-cueit *)::after {
background-color: var(--ds-accent);
}
/* Floating surfaces: thick material, 12px corners, elev-lg. They scale
from 96% rather than sliding. */
:is([data-slot="select-content"], [data-slot="dropdown-menu-content"], [data-slot="dropdown-menu-sub-content"]):where(.ds-cueit, .ds-cueit *) {
border-radius: 12px;
corner-shape: squircle;
border-color: var(--ds-separator);
background-color: var(--ds-material-thick);
backdrop-filter: var(--ds-material-blur);
-webkit-backdrop-filter: var(--ds-material-blur);
box-shadow: var(--ds-elev-lg);
}
:is([data-slot="select-item"], [data-slot="dropdown-menu-item"], [data-slot="dropdown-menu-checkbox-item"], [data-slot="dropdown-menu-radio-item"], [data-slot="dropdown-menu-sub-trigger"]):where(.ds-cueit, .ds-cueit *) {
border-radius: var(--ds-radius-chip);
font-size: 13px;
line-height: 18px;
}
:is([data-slot="select-item"], [data-slot="dropdown-menu-item"], [data-slot="dropdown-menu-checkbox-item"], [data-slot="dropdown-menu-radio-item"], [data-slot="dropdown-menu-sub-trigger"]):where(.ds-cueit, .ds-cueit *):is(:focus, [data-highlighted], [data-state="open"]) {
background-color: var(--ds-fill-quiet);
color: var(--ds-text-primary);
}
[data-slot="select-item"][data-state="checked"]:where(.ds-cueit, .ds-cueit *) {
background-color: var(--ds-accent-tint);
color: var(--ds-accent-on-tint);
}
:is([data-slot="select-label"], [data-slot="dropdown-menu-label"]):where(.ds-cueit, .ds-cueit *) {
font-family: var(--ds-font-mono);
font-size: 11px;
line-height: 14px;
letter-spacing: 0.9px;
font-weight: 500;
text-transform: uppercase;
color: var(--ds-text-tertiary);
}
:is([data-slot="select-separator"], [data-slot="dropdown-menu-separator"]):where(.ds-cueit, .ds-cueit *) {
background-color: var(--ds-separator);
}
/* Tooltip: floats above content, so it takes a material rather than a dark
fill. No arrow. */
[data-slot="tooltip-content"]:where(.ds-cueit, .ds-cueit *) {
border-radius: var(--ds-radius-control);
corner-shape: squircle;
border: 1px solid var(--ds-separator);
background-color: var(--ds-material-thick);
backdrop-filter: var(--ds-material-blur);
-webkit-backdrop-filter: var(--ds-material-blur);
color: var(--ds-text-primary);
box-shadow: var(--ds-elev-lg);
padding: 6px 10px;
font-size: 12px;
line-height: 16px;
font-weight: 500;
}
[data-slot="tooltip-content"]:where(.ds-cueit, .ds-cueit *) > svg {
display: none;
}
/* Dialog: a sheet. 24px corners on the raised surface, elev-xl. */
[data-slot="dialog-content"]:where(.ds-cueit, .ds-cueit *) {
border-radius: var(--ds-radius-sheet);
corner-shape: squircle;
border-color: var(--ds-separator);
background-color: var(--ds-bg-raised);
box-shadow: var(--ds-elev-xl);
}
[data-slot="dialog-overlay"]:where(.ds-cueit, .ds-cueit *) {
background-color: var(--ds-scrim);
}
[data-slot="dialog-title"]:where(.ds-cueit, .ds-cueit *) {
font-size: 21px;
line-height: 26px;
letter-spacing: -0.3px;
font-weight: 600;
}
[data-slot="dialog-description"]:where(.ds-cueit, .ds-cueit *) {
font-size: 13px;
line-height: 18px;
}
[data-slot="dialog-close"]:where(.ds-cueit, .ds-cueit *) {
border-radius: var(--ds-radius-control);
}
/* Table: mono-label headers, hairline rows, a quiet hover fill. */
[data-slot="table"]:where(.ds-cueit, .ds-cueit *) {
font-size: 13px;
line-height: 18px;
}
[data-slot="table-head"]:where(.ds-cueit, .ds-cueit *) {
font-family: var(--ds-font-mono);
font-size: 11px;
line-height: 14px;
letter-spacing: 0.9px;
font-weight: 500;
text-transform: uppercase;
color: var(--ds-text-tertiary);
}
[data-slot="table-row"]:where(.ds-cueit, .ds-cueit *) {
border-color: var(--ds-separator);
transition: background-color var(--ds-dur-hover) var(--ds-ease);
}
[data-slot="table-row"]:where(.ds-cueit, .ds-cueit *):hover {
background-color: var(--ds-fill-quiet);
}
[data-slot="table-row"][data-state="selected"]:where(.ds-cueit, .ds-cueit *) {
background-color: var(--ds-accent-tint);
}
/* Smaller parts */
[data-slot="skeleton"]:where(.ds-cueit, .ds-cueit *) {
border-radius: var(--ds-radius-field);
background-color: var(--ds-bg-sunken);
}
[data-slot="progress"]:where(.ds-cueit, .ds-cueit *) {
height: 6px;
border-radius: var(--ds-radius-pill);
background-color: var(--ds-bg-sunken);
}
[data-slot="progress-indicator"]:where(.ds-cueit, .ds-cueit *) {
border-radius: var(--ds-radius-pill);
background-color: var(--ds-accent-fill);
transition: transform var(--ds-dur-progress) var(--ds-ease);
}
[data-slot="alert"]:where(.ds-cueit, .ds-cueit *) {
border-radius: var(--ds-radius-tile);
corner-shape: squircle;
border-color: var(--ds-separator);
background-color: var(--ds-bg-surface);
font-size: 13px;
line-height: 18px;
}
[data-slot="kbd"]:where(.ds-cueit, .ds-cueit *) {
border-radius: var(--ds-radius-chip);
background-color: var(--ds-bg-sunken);
font-family: var(--ds-font-mono);
font-size: 11px;
color: var(--ds-text-secondary);
}
[data-slot="avatar-fallback"]:where(.ds-cueit, .ds-cueit *) {
background-color: var(--ds-bg-sunken);
color: var(--ds-text-secondary);
font-size: 12px;
font-weight: 500;
}
[data-slot="separator"]:where(.ds-cueit, .ds-cueit *) {
background-color: var(--ds-separator);
}
/* -------------------------------------------------------------------------
08 BLOCK CONTRACT VALUES
What the shared ds-* utilities in section 09 read, said in Cueit's
vocabulary. A block written against the contract becomes a Cueit screen:
material chrome on the canvas, squircles, mono labels, an orange tile.
------------------------------------------------------------------------- */
html.ds-cueit,
.ds-cueit {
--ds-corner: squircle;
--ds-ink: var(--ds-accent);
--ds-ink-tint: var(--ds-accent-tint);
--ds-emphasis: var(--ds-brand);
--ds-on-emphasis: var(--ds-brand-contrast);
--ds-emphasis-shadow: var(--ds-key-light), var(--ds-elev-md);
--ds-quiet-fill: var(--ds-fill-quiet);
/* Shell: translucent chrome over the canvas; content sits on the canvas
itself, with white cards on it. */
--ds-shell-canvas: var(--ds-bg-canvas);
--ds-shell-panel: var(--ds-bg-canvas);
--ds-shell-edge: var(--ds-separator);
--ds-shell-radius: var(--ds-radius-card);
--ds-shell-inset: 0px;
--ds-panel-radius: 0px;
--ds-panel-ring: none;
--ds-sidebar-bg: var(--ds-material);
--ds-sidebar-blur: var(--ds-material-blur);
--ds-sidebar-edge: var(--ds-separator);
--ds-topbar-bg: var(--ds-material);
--ds-topbar-blur: var(--ds-material-blur);
--ds-nav-active-bg: var(--ds-nav-selected);
--ds-nav-active-edge: var(--ds-nav-selected-border);
--ds-nav-active-fg: var(--ds-text-primary);
/* Shape */
--ds-r-control: var(--ds-radius-control);
--ds-r-surface: var(--ds-radius-card);
--ds-surface-edge: var(--ds-separator);
--ds-surface-shadow: var(--ds-elev-sm);
/* Type: title2 for page titles, title1 for numbers, the mono motif for
labels and values. */
--ds-text-size: 15px;
--ds-text-leading: 22px;
--ds-small-size: 13px;
--ds-small-leading: 18px;
--ds-nav-size: 13px;
--ds-title-font: var(--ds-font-sans);
--ds-title-size: 26px;
--ds-title-leading: 32px;
--ds-title-tracking: -0.45px;
--ds-title-weight: 700;
--ds-subtitle-size: 15px;
--ds-subtitle-leading: 22px;
--ds-stat-font: var(--ds-font-sans);
--ds-stat-size: 32px;
--ds-stat-leading: 38px;
--ds-stat-tracking: -0.6px;
--ds-stat-weight: 700;
--ds-label-font: var(--ds-font-mono);
--ds-label-size: 11px;
--ds-label-leading: 14px;
--ds-label-tracking: 0.9px;
--ds-label-weight: 500;
--ds-label-case: uppercase;
--ds-label-color: var(--ds-text-tertiary);
--ds-meta-font: var(--ds-font-mono);
--ds-meta-size: 11px;
--ds-meta-tracking: 0.2px;
--ds-meta-color: var(--ds-text-tertiary);
}
/* <block-contract> --------------------------------------------------------
09 BLOCK CONTRACT (shared — identical in every Satori UI system)
Blocks (components/satori-ui/blocks) render in whichever system scopes
them. They read shadcn's tokens, the tint-* utilities and the ds-*
utilities below, never a system's own names. Each utility reads only
--ds-* variables, with a shadcn fallback, and each system sets those
variables in its own section 08. This section must stay byte for byte
the same in every system file: `node design-systems/check-contract.mjs`
fails if two copies drift.
The utilities set skin only. Layout (flex, padding, gaps, widths) stays
in the block's markup, so a className still wins. The one exception is
ds-shell-panel's margin: whether the content panel is inset on the
canvas is part of a system's look, not of the block.
------------------------------------------------------------------------- */
/* App frame: the outer edge and the ground the sidebar sits on. */
@utility ds-shell {
border: 1px solid var(--ds-shell-edge, var(--border));
border-radius: var(--ds-shell-radius, var(--radius));
corner-shape: var(--ds-corner, round);
background-color: var(--ds-shell-canvas, var(--background));
color: var(--foreground);
}
@utility ds-shell-sidebar {
background-color: var(--ds-sidebar-bg, transparent);
backdrop-filter: var(--ds-sidebar-blur, none);
-webkit-backdrop-filter: var(--ds-sidebar-blur, none);
border-right: 1px solid var(--ds-sidebar-edge, transparent);
}
@utility ds-shell-panel {
margin: var(--ds-shell-inset, 0) var(--ds-shell-inset, 0) var(--ds-shell-inset, 0) 0;
border-radius: var(--ds-panel-radius, 0);
corner-shape: var(--ds-corner, round);
background-color: var(--ds-shell-panel, var(--background));
box-shadow: var(--ds-panel-ring, none);
}
@utility ds-shell-topbar {
background-color: var(--ds-topbar-bg, transparent);
backdrop-filter: var(--ds-topbar-blur, none);
-webkit-backdrop-filter: var(--ds-topbar-blur, none);
border-bottom: 1px solid var(--border);
}
/* Navigation rows. The current page is marked with aria-current="page". */
@utility ds-nav-item {
border: 1px solid transparent;
border-radius: var(--ds-r-control, calc(var(--radius) - 2px));
corner-shape: var(--ds-corner, round);
color: var(--muted-foreground);
font-size: var(--ds-nav-size, 0.875rem);
font-weight: 500;
transition:
background-color var(--ds-dur-hover, 150ms) ease,
color var(--ds-dur-hover, 150ms) ease;
&:hover {
background-color: var(--ds-quiet-fill, var(--accent));
color: var(--foreground);
}
&[aria-current="page"] {
background-color: var(--ds-nav-active-bg, var(--accent));
border-color: var(--ds-nav-active-edge, transparent);
color: var(--ds-nav-active-fg, var(--foreground));
}
}
/* The product's mark: the one place the emphasis colour appears in chrome. */
@utility ds-mark {
border-radius: var(--ds-r-control, calc(var(--radius) - 2px));
corner-shape: var(--ds-corner, round);
background-color: var(--ds-emphasis, var(--primary));
color: var(--ds-on-emphasis, var(--primary-foreground));
font-family: var(--ds-title-font, inherit);
font-weight: 600;
}
/* Type. The roles a block needs that shadcn has no name for. Blocks set
copy with ds-text and ds-small rather than text-sm / text-xs, because a
system may re-point Tailwind's steps (Skita draws at 110%). */
@utility ds-text {
font-size: var(--ds-text-size, 0.875rem);
line-height: var(--ds-text-leading, 1.5);
}
@utility ds-small {
font-size: var(--ds-small-size, 0.8125rem);
line-height: var(--ds-small-leading, 1.45);
}
@utility ds-title {
font-family: var(--ds-title-font, inherit);
font-size: var(--ds-title-size, 1.5rem);
line-height: var(--ds-title-leading, 1.25);
letter-spacing: var(--ds-title-tracking, -0.01em);
font-weight: var(--ds-title-weight, 600);
text-transform: var(--ds-title-case, none);
}
@utility ds-subtitle {
font-size: var(--ds-subtitle-size, 0.875rem);
line-height: var(--ds-subtitle-leading, 1.5);
color: var(--muted-foreground);
}
@utility ds-stat {
font-family: var(--ds-stat-font, var(--ds-title-font, inherit));
font-size: var(--ds-stat-size, 1.875rem);
line-height: var(--ds-stat-leading, 1.2);
letter-spacing: var(--ds-stat-tracking, -0.02em);
font-weight: var(--ds-stat-weight, 600);
font-variant-numeric: tabular-nums;
}
/* Data about the content: KPI labels, group headings, column captions. */
@utility ds-label {
font-family: var(--ds-label-font, inherit);
font-size: var(--ds-label-size, 0.6875rem);
line-height: var(--ds-label-leading, 1.3);
letter-spacing: var(--ds-label-tracking, 0.06em);
font-weight: var(--ds-label-weight, 500);
text-transform: var(--ds-label-case, uppercase);
font-variant-numeric: tabular-nums;
color: var(--ds-label-color, var(--muted-foreground));
}
/* A small value: timestamps, IDs, amounts, lengths. */
@utility ds-meta {
font-family: var(--ds-meta-font, inherit);
font-size: var(--ds-meta-size, 0.75rem);
letter-spacing: var(--ds-meta-tracking, 0);
font-variant-numeric: tabular-nums;
color: var(--ds-meta-color, var(--muted-foreground));
}
@utility ds-link {
color: var(--ds-ink, var(--primary));
font-weight: 500;
text-underline-offset: 3px;
&:hover {
text-decoration-line: underline;
}
}
@utility ds-ink {
color: var(--ds-ink, var(--primary));
}
/* Surfaces a block draws itself (a KPI tile, a plan, an empty state). */
@utility ds-tile {
border: 1px solid var(--ds-surface-edge, var(--border));
border-radius: var(--ds-r-surface, var(--radius));
corner-shape: var(--ds-corner, round);
background-color: var(--card);
color: var(--card-foreground);
box-shadow: var(--ds-surface-shadow, none);
}
/* The one emphasised surface a view gets. Labels and meta inside it take
the label colour, so ds-label and ds-meta read on the fill. */
@utility ds-tile-emphasis {
border: 1px solid transparent;
border-radius: var(--ds-r-surface, var(--radius));
corner-shape: var(--ds-corner, round);
background-color: var(--ds-emphasis, var(--primary));
color: var(--ds-on-emphasis, var(--primary-foreground));
box-shadow: var(--ds-emphasis-shadow, none);
--ds-label-color: currentColor;
--ds-meta-color: currentColor;
--muted-foreground: color-mix(in oklab, currentColor 75%, transparent);
}
/* An icon in a wash of the action colour. */
@utility ds-well {
border-radius: var(--ds-r-control, calc(var(--radius) - 2px));
corner-shape: var(--ds-corner, round);
background-color: var(--ds-ink-tint, var(--muted));
color: var(--ds-ink, var(--primary));
}
/* A row or card that highlights on hover with the system's quiet fill. */
@utility ds-hover {
transition: background-color var(--ds-dur-hover, 150ms) ease;
&:hover {
background-color: var(--ds-quiet-fill, var(--accent));
}
}
/* </block-contract> ------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
.ds-cueit *,
.ds-cueit *::before,
.ds-cueit *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}