Skinning Control UI
Author complete token-driven Control UI skins with slots, adornments, motion controls, and one shared component source.
Not sure a skin is even the right tool? The customization ladder in the Architecture guide places every "make it different" request on one of eight rungs — with what each rung costs an app that never uses it. This page picks up once the answer is a skin.
Where skin values live
One owner for each visual decision.
The token contract
Every custom property a skin may re-value, rendered straight from lib/theme-contract.ts — the same module the coverage test enforces, so this reference can never drift from the CSS.
--backgroundColorBase surface color (panels, bubbles read it via bg-background).--foregroundColorDefault text color on --background.--cardColorElevated card surface.--card-foregroundColorText color on --card.--popoverColorFloating surface base (popover / menu / select / dialog).--popover-foregroundColorText color on --popover.--primaryColorTHE brand color; primary action surfaces route through it.--primary-foregroundColorText color on --primary.--mutedColorSubdued fill for quiet surfaces.--muted-foregroundColorSecondary / meta text color.--secondaryColorSecondary action fill (assistant bubble in chat skins).--secondary-foregroundColorText color on --secondary.--accentColorHover / selection highlight fill.--accent-foregroundColorText color on --accent.--destructiveColorDestructive action color.--destructive-foregroundColorText color on --destructive.--borderColorHairline border color (carries --ring-opacity).--inputColorForm field border color.--ringColorFocus ring color.--canvasColorThe page paper the scene/panels float on — a level BELOW --background.--font-sansTypographyTypeface for the whole UI.--font-monoTypographyMonospace face (code, kbd).--radiusRadiusTHE single radius knob; the whole scale multiplies from it.--duration-fastMotionFast tempo (hover, color).--duration-baseMotionBase tempo (menus, indicators).--duration-slowMotionSlow tempo (panel/message entrances).Derived — optional overrides80 tokens with a core default; re-value one in theme.css only to diverge›
--primary-textColorReadable brand text color on base and card surfaces.--destructive-textColorReadable destructive text color on base and card surfaces.--focus-ringColorColor of the keyboard focus indicator; defaults to --ring. Must clear 3:1 against every surface it lands on (WCAG 1.4.11).--ring-opacityColorAlpha of the border/ring hairlines; 0 = borderless, defaults to 1.--badge-neutralColorSoft neutral-family badge background; the skin owns the exact hue.--badge-neutral-foregroundColorText color on the neutral-family badge.--badge-neutral-borderColorBorder of the outline neutral-family badge variant.--badge-neutral-hoverColorHover background of filled neutral-family badge links and buttons.--badge-redColorSoft red-family badge background; the skin owns the exact hue.--badge-red-foregroundColorText color on the red-family badge.--badge-red-borderColorBorder of the outline red-family badge variant.--badge-red-hoverColorHover background of filled red-family badge links and buttons.--badge-orangeColorSoft orange-family badge background; the skin owns the exact hue.--badge-orange-foregroundColorText color on the orange-family badge.--badge-orange-borderColorBorder of the outline orange-family badge variant.--badge-orange-hoverColorHover background of filled orange-family badge links and buttons.--badge-yellowColorSoft yellow-family badge background; the skin owns the exact hue.--badge-yellow-foregroundColorText color on the yellow-family badge.--badge-yellow-borderColorBorder of the outline yellow-family badge variant.--badge-yellow-hoverColorHover background of filled yellow-family badge links and buttons.--badge-greenColorSoft green-family badge background; the skin owns the exact hue.--badge-green-foregroundColorText color on the green-family badge.--badge-green-borderColorBorder of the outline green-family badge variant.--badge-green-hoverColorHover background of filled green-family badge links and buttons.--badge-blueColorSoft blue-family badge background; the skin owns the exact hue.--badge-blue-foregroundColorText color on the blue-family badge.--badge-blue-borderColorBorder of the outline blue-family badge variant.--badge-blue-hoverColorHover background of filled blue-family badge links and buttons.--badge-purpleColorSoft purple-family badge background; the skin owns the exact hue.--badge-purple-foregroundColorText color on the purple-family badge.--badge-purple-borderColorBorder of the outline purple-family badge variant.--badge-purple-hoverColorHover background of filled purple-family badge links and buttons.--badge-pinkColorSoft pink-family badge background; the skin owns the exact hue.--badge-pink-foregroundColorText color on the pink-family badge.--badge-pink-borderColorBorder of the outline pink-family badge variant.--badge-pink-hoverColorHover background of filled pink-family badge links and buttons.--font-bodyTypographyFont ROLE for body/UI text; defaults to --font-sans.--font-displayTypographyFont ROLE for headings; defaults to --font-sans.--text-microTypography10px rung — kbd, badge counters, dense numeric meta.--text-captionTypography11px rung — overlines, timestamps, group labels.--text-bodyTypography14px rung — DEFAULT body & control text.--text-body-lgTypography16px rung — emphasized / larger body.--text-heading-4TypographySmallest heading rung (15px).--text-heading-4--line-heightTypographyLine-height paired onto text-heading-4.--text-heading-4--font-weightTypographyWeight paired onto text-heading-4.--text-heading-3TypographyHeading rung (18px).--text-heading-3--line-heightTypographyLine-height paired onto text-heading-3.--text-heading-3--font-weightTypographyWeight paired onto text-heading-3.--text-heading-2TypographyHeading rung (22px).--text-heading-2--line-heightTypographyLine-height paired onto text-heading-2.--text-heading-2--font-weightTypographyWeight paired onto text-heading-2.--text-heading-1TypographyLargest content heading (30px — in-page / markdown h1).--text-heading-1--line-heightTypographyLine-height paired onto text-heading-1.--text-heading-1--font-weightTypographyWeight paired onto text-heading-1.--text-heading-1--letter-spacingTypographyTracking paired onto text-heading-1.--text-displayTypographyDisplay rung above h1 (36px — page titles, heroes).--text-display--line-heightTypographyLine-height paired onto text-display.--text-display--font-weightTypographyWeight paired onto text-display.--text-display--letter-spacingTypographyTracking paired onto text-display.--radius-smRadiusScale rung: --radius × 0.6.--radius-mdRadiusScale rung: --radius × 0.8.--radius-lgRadiusScale rung: --radius × 1.--radius-xlRadiusScale rung: --radius × 1.4.--radius-2xlRadiusScale rung: --radius × 1.6.--corner-shapeRadiusProgressive corner reshape (round | squircle | scoop | …); defaults to round.--corner-radius-fitRadiusFallback radius shrink where corner-shape: squircle is unsupported.--radius-popoverRadiusPopup container corner, concentric with the fitted row corner.--shadow-highlightShadowInner top light painted along raised surfaces' top edge (carries its resting alpha).--shadow-yShadowVertical bias: 0 = centered, 1 = default bottom cast.--shadow-control-multiplierShadowElevation tier: controls; defaults to 1.--shadow-panel-multiplierShadowElevation tier: panels; defaults to 2.--shadow-popover-multiplierShadowElevation tier: floating popovers; defaults to 4.--shadow-modal-multiplierShadowElevation tier: dialogs and sheets; defaults to 5.--shadow-ambient-multiplierShadowElevation tier: ambient scene lift; defaults to 8.--control-h-xsLayoutDerived control height: xs (×0.78, px-snapped).--control-h-smLayoutDerived control height: sm (×0.89, px-snapped).--control-h-lgLayoutDerived control height: lg (×1.11, px-snapped).--focus-ring-widthLayoutThickness of the keyboard focus indicator; 0 removes it and fails WCAG 2.4.7.--focus-ring-styleLayoutLine style of the keyboard focus indicator (solid, dotted, dashed); none removes it and fails WCAG 2.4.7.--focus-ring-offsetLayoutGap between a control edge and its focus indicator; negative draws the indicator inside.Advanced — Typography1 token›
--text-labelTypography12px rung — form labels + small chrome.Advanced — Radius5 tokens›
--radius-controlRadiusShared control corner (button, trigger, field, chip); ×2 from --radius.--radius-fieldRadiusUser bubble / composer shell corner; --radius × 2.2.--radius-panelRadiusCode / markdown panel corner; --radius × 2.6.--radius-sceneRadiusScene frame / large media corner; --radius × 2.8.--radius-popup-itemRadiusSelect/menu row corner; derived from --radius-control.Advanced — Shadow3 tokens›
--shadow-colorShadowHue every shadow is tinted with.--shadow-sizeShadowGlobal geometry multiplier: 1 = default depth, 0 = flat.--shadow-opacityShadowGlobal alpha multiplier: 1 = default density, 0 = invisible.Advanced — Motion2 tokens›
--ease-standardMotionDefault easing curve for color/text transitions.--ease-emphasizedMotionEmphasized curve for entrances and larger moves.Advanced — Surface5 tokens›
--popover-opacitySurfaceFloating-surface translucency; <1 + blur = frosted glass.--backdrop-blur-popoverSurfaceBackdrop blur behind floating surfaces.--overlay-opacitySurfaceModal overlay (dialog backdrop) dim strength.--backdrop-blur-overlaySurfaceBackdrop blur of the modal overlay.--scroll-fade-sizeSurfaceEdge-fade depth of scrollable surfaces; 0 = hard edges.Advanced — Layout4 tokens›
--popover-paddingLayoutGap between popup container edge and rows (drives concentric corners).--padding-xLayoutHorizontal content density of rounded surfaces (bubble, composer).--padding-yLayoutVertical content density of rounded surfaces.--control-hLayoutTHE base control height (md); the ramp derives from it.The anatomy contract
Generated from canonical component DOM. Family is the selector key; slot names the part; scope identifies the component.
Copy a selector as [data-skin="brand"] [data-slot="shell"][data-control-family="chat-composer"]. Optional adornment anchors are listed separately in the full machine-readable contract, so they cannot be mistaken for DOM parts.
accordion (5)
rooticonitempaneltriggeraction-bar (1)
rootactivity (13)
rootannouncementchevroncontentcontent-viewportdetaildetail-contentdetail-labeliconrowstatustitletriggeralert (3)
rootdescriptiontitlealert-dialog (7)
backdropcontentdescriptionfooterheadertitletriggeraspect-ratio (1)
rootaudio-recorder (9)
rootactionscancelcontentdurationstatussubmittriggervisualizeraudio-visualizer (5)
rootbarbar-trackbaselinetrackaudio-visualizer-line (1)
waveformautocomplete (10)
rootclearcontentemptygroupgroup-labelinputitemlistpositioneravatar (4)
rootfallbackgroupimagebadge (1)
rootbreadcrumb (7)
rootellipsisitemlinklistpageseparatorbutton (2)
rootcontentbutton-group (3)
rootseparatortextcalendar (11)
rootcaption-labeldayday-cellmonthmonth-captionmonth-gridmonthsnavnav-buttonweekdaycard (7)
rootactioncontentdescriptionfooterheadertitlechat-composer (12)
rootaccentfootermentionmention-descriptionmention-iconmention-labelshellsubmittextareatoolbartoolschat-composer-attachment (10)
rootcontentdescriptionpreviewpreview-labelprogressprogress-indicatorremovestatustitlechat-composer-attachments (3)
rootlistscrollchat-composer-editor (4)
rooteditorfallbackplaceholderchat-layout (1)
rootchat-message (7)
rootactionsavatarbodycontentheaderrowchat-thought (5)
rootchevrondetailstitletriggerchat-thread (1)
rootchat-turn (1)
turncheckbox (2)
rootindicatorcheckbox-group (1)
rootcode (9)
rootaccessible-sourceactionscontentgridgutterheaderlinetitlecode-block-editor (6)
rootactionscontenteditorheadertitlecode-diff (21)
rootaccessible-sourceactionsbodyemphasisempty-halfexpand-buttonexpanderexpander-labelfilefile-headerfile-titlegutterheaderlinemarkerrowstatstat-additionsstat-deletionstitlecollapsible (3)
rootcontenttriggercolor-picker (40)
alphaalpha-thumbalpha-trackareaarea-brightnessarea-saturationarea-thumbchannelchannel-labelchannelscontentcontrastcontrast-fixcontrast-levelcontrast-ratioeye-dropperformathuehue-thumbhue-trackinputoutputoutput-checkeroutput-coloroutput-swatchoutput-valuepanelpositionerswatchswatch-addswatch-checkerswatch-colorswatchesswatches-groupswatches-labeltriggertrigger-checkertrigger-colorwheelwheel-thumbcombobox (12)
rootcontentemptygroupgroup-labeliconinputitemitem-indicatorlistpositionertriggercommand (11)
rootdialog-rootemptygroupinputinput-iconinput-wrapperitemlistseparatorshortcutcontext (23)
rootcontentdescriptiongraphheaderlegendlegend-descriptionlegend-indicatorlegend-itemlegend-labellegend-valuelimit-markeroveragesegmentsummarysummary-valuetitletracktriggertrigger-indicatortrigger-labeltrigger-tracktrigger-valuecontext-menu (14)
checkbox-itemcontentgroupitemlabelradio-groupradio-indicatorradio-itemseparatorshortcutsub-contentsub-triggersub-trigger-indicatortriggerdialog (7)
backdropcontentdescriptionfooterheadertitletriggerdockable-panel (7)
rootactionscontentdrag-handledrop-zoneheadertitledrawer (10)
backdropclosecontentdescriptionfooterhandleheadertitletriggerviewportdropdown-menu (5)
contentitemlabelseparatortriggerdropzone (14)
rootareafeedbackfeedback-iconfeedback-messagefeedback-spinnerfilefile-listinputoverlayrejectionrejection-liststatustriggerdynamic-notification (14)
rootclosecontentglassindicatorislandliquidmessagepillreplyreply-inputreply-submittitlewordempty (6)
rootcontentdescriptionheadermediatitleenvironment-variables (22)
rootactionscolumn-labelsdescriptionemptyfield-errorfield-labelheaderhinthint-codekey-inputmessagereadonly-itemreadonly-keyreadonly-listreadonly-valuerowrowstitletoolbarvalue-groupvalue-inputfield (13)
rootcontentcontroldescriptionerrorgroupitemlabellegendseparatorseparator-contentsettitleform (1)
rootgradient-editor (5)
rootpreviewstopstop-addtrackhover-card (3)
contentpositionertriggerinfinite-canvas (3)
rootcontentcontrolsinline-attachment (14)
rootactionactionscontentdescriptiondocumentdocument-headingdocument-linedocument-sheetdocument-stampimagemedia-imageplaceholdertitleinline-citation (18)
rootcontentfaviconfaviconslabelnavigationnextpositionprevioussourcesource-descriptionsource-external-iconsource-faviconsource-headersource-hostnamesource-quotesource-titletriggerinput (1)
rootinput-group (3)
rootaddoninputinput-otp (3)
rootseparatorslotitem (10)
rootactionscontentdescriptionfootergroupheadermediaseparatortitlekbd (2)
rootgrouplabel (1)
rootmarkdown (18)
rootblockquoteemphasish1h2h3inline-codelinklist-itemordered-listparagraphseparatorstrongtabletable-celltable-headertable-scrollunordered-listmarkdown-block (5)
rootcontentheadertitletitle-iconmenubar (11)
rootcontentgroupitemlabelseparatorshortcutsub-contentsub-triggersub-trigger-indicatortriggermeter (5)
rootindicatorlabeltrackvaluemodel-switcher (4)
roothintindicatorvaluemorphing-panel (3)
rootcontenttriggernative-select (2)
rooticonnavigation-menu (10)
rootcontenticonitemlinklistpopuppositionertriggerviewportnumber-field (7)
decrementdrag-icongroupincrementinputscrub-areascrub-cursorpagination (5)
rootcontentellipsisitemlinkphone-input (6)
checkchevroncountry-codecountry-triggerflagmetadatapopover (6)
closecontentdescriptionheadertitletriggerprogress (5)
rootindicatorlabeltrackvalueradio-group (3)
rootindicatoritemresizable (4)
handlehandle-grippanelpanel-grouprich-tooltip (14)
arrowclosecontentdescriptiondotfooterheadermedianextpositionerpreviousprogresstitletriggerscroll-area (6)
rootcontentcornerscrollbarthumbviewportselect (5)
contenticonitemitem-indicatortriggerseparator (1)
rootsheet (6)
backdropclosecontentdescriptionheadertitlesidebar (17)
rootcontainercontentfootergapgroupgroup-labelheaderinnerinsetmenumenu-buttonmenu-itemmenu-trackrailtriggerwrappersidebar-layout (1)
contentskeleton (1)
rootslider (8)
rootcontrolindicatorlabelthumbticktrackvaluesource-badge (2)
rootlabelspinner (2)
rootindicatorstepper (9)
rootcontentdescriptionindicatoritemlistseparatortitletriggerswitch (3)
rootthumbthumb-icontable (9)
rootbodycaptioncellcontainerfooterheadheaderrowtable-of-contents (7)
rootitemlabellistrailtracktrailtabs (5)
rootindicatorlistpaneltabtask-list (8)
rootchevronitemitem-indicatoritemslabelprogresstriggertextarea (1)
rootthread-rail (11)
rootfilefile-iconfooteritemlinemorepopoverpopover-layersummarytitletimeline (8)
rootcontentdescriptionindicatoritemmetaseparatortitletoast (8)
rootactionclosecontentdescriptionindicatortitleviewporttoggle (4)
rootcheckcontentgrouptoolbar (6)
rootbuttongroupinputlinkseparatortooltip (4)
arrowarrow-shapecontentpositionertrack-highlight (2)
roothovertranscript-divider (2)
rootlabeltree (8)
rootitemitem-contentitem-groupitem-indicatoritem-labelitem-triggertracktrigger-menu (8)
rootemptygroupgroup-labeliconitemlistpositioneruser-ask (16)
rootdismissfooterfreeform-labelheaderoptionoption-descriptionoption-indicatoroption-inputoption-labelpaginationpagination-countquestionrecommendedsubmittitleSkin requirements — the do-not list
What a pack must and must not do. Every rule is enforced by a test, so a violating pack fails the build before it ships.
Adornments
JSX at named anchors — decorative chrome, or behavioral fx layers.
Additive, render zero DOM when absent, never gate library behavior. Decorative anchors (window titlebars, window controls) take plain aria-hidden JSX. Behavioral anchors (like chat-composer:send-layer) take a (ctx) => JSX render prop whose ctx carries plain render-time values — this is how a pack activates an anchored extension (send-aurora). Adornments are nested by scope and part in skin.config. Interactive values live in a separate "use client" file the pack ships, so skin.config itself stays RSC-pure.
Motion flag
One boolean, zero JavaScript.
motion: "reduced" stamps data-motion="reduced", which collapses --duration-fast/base/slow to 0 — every cva transition, Base UI enter/exit and the shimmer/ripple keyframes flatten at once. The theme editor also exposes a manual "Reduce motion" toggle.
Component knobs and the cascade
Tokens theme the whole app from :root. Knobs theme one family and live on its root element — every slot inherits.
/* app-wide: a token, on :root */:root { --radius-field: 0.75rem; }/* one family, everywhere: a knob, on the family root */[data-control-family="chat-composer"] { --cui-chat-composer-shell-radius: 0; }/* one instance, on the root element: className="[--cui-chat-composer-shell-radius:0]" or style={{ "--cui-chat-composer-shell-radius": "0" }} */The recipe declares each knob's default on the family root at zero specificity, so a value set on that element always wins: style beats a skin, a skin beats the recipe, a utility class beats both. Setting a knob on :root does nothing — the family root re-declares it; re-value the token it derives from instead. Portaled surfaces are their own root: set --popup-* on the popup content, not on the trigger. Each component page lists its knobs with type and default.
Skins
Each pack's tokens, skin.css and config, ready to read and install.