Skip to content

Commit d711a88

Browse files
committed
docs(Theming): clarify theme builder
1 parent e67080d commit d711a88

1 file changed

Lines changed: 46 additions & 34 deletions

File tree

‎src/stories/Theming.stories.tsx‎

Lines changed: 46 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ function StoryPage({
251251
<Page>
252252
<Section>
253253
<Heading>{title}</Heading>
254-
<Lead>{description}</Lead>
254+
<Lead as="div">{description}</Lead>
255255
</Section>
256256
{children}
257257
</Page>
@@ -317,7 +317,7 @@ function ColorResolution({ resolved }: { resolved?: Tokens }) {
317317
{palette.pastel ? (
318318
<InfoBadge
319319
theme="danger"
320-
tooltip="Pastel caps chroma, so this color cannot render exactly — the palette takes its hue and its tone and finds the nearest color inside the ceiling. Turn Pastel off to land on it."
320+
tooltip="Pastel limits color intensity, so the requested color is adjusted to the closest available result. Switch to Advanced or Color to use the full chroma range."
321321
/>
322322
) : null}
323323
</Row>
@@ -469,7 +469,7 @@ function AccentSourceControls({ resolved }: { resolved?: Tokens }) {
469469
<ColorInput
470470
label="Color"
471471
size="small"
472-
tooltip="Hue, chroma and tone all come from here — the tone is what makes the brand fill actually be your color rather than a shade re-derived at a fixed lightness. Glaze reproduces it exactly in the light, normal-contrast variant; dark and high contrast adapt. Two APCA floors apply everywhere, and they are different sizes: Lc 45 against the white label it carries, because that is text, and Lc 25 against the page, because a fill is a shape. Both are APCA rather than WCAG, so a fill can legitimately sit under 3:1 — #0EA5E9 lands at 2.77:1 and is correct there."
472+
tooltip="Sets the accent hue, intensity, and tone from one hex color. The light, normal-contrast fill matches it when accessibility limits allow; dark and high-contrast variants adapt automatically. Colors that are too light for white text or too subtle against the page are adjusted."
473473
value={seedColor(input.accent)}
474474
onChange={(accent) =>
475475
// Clearing the field is a change of path, so it lands back on a hue seed
@@ -497,7 +497,7 @@ function AccentSourceControls({ resolved }: { resolved?: Tokens }) {
497497
// No value in the label: the slider already prints it on the right, and
498498
// the same number twice on one line reads as two facts.
499499
label="Hue"
500-
tooltip="Drives the whole accent family, `primary` / `purple` / `special`, and the brand-tinted odds and ends — the focus ring, the loading faces, the disabled chip."
500+
tooltip="Sets the brand hue used by the accent, primary, purple, and special colors, plus focus and loading states."
501501
value={Math.round(palette.hue)}
502502
onChange={(hue) =>
503503
setPalette((config) => ({
@@ -511,7 +511,7 @@ function AccentSourceControls({ resolved }: { resolved?: Tokens }) {
511511
{palette.pastel ? null : (
512512
<Slider
513513
label="Saturation"
514-
tooltip="The accent zone's chroma, and the fallback every status theme inherits until it sets its own. It is also a ceiling on the base zone, which takes a 12% share of it while it follows the accent."
514+
tooltip="Controls the intensity of accent colors. Status themes inherit this value until customized; the base palette uses a small share while it follows the accent."
515515
value={palette.saturation}
516516
onChange={(saturation) =>
517517
setPalette((config) => ({
@@ -547,6 +547,7 @@ function BaseSourceControls() {
547547
labelPosition="split"
548548
type="button"
549549
value={isOwn ? 'own' : 'accent'}
550+
tooltip="Follow accent derives neutral UI colors from the accent seed. Own lets you set a separate base hue or color for surfaces, text, borders, and placeholders."
550551
onChange={(next) =>
551552
setPalette(({ base, ...config }) =>
552553
next === 'own'
@@ -573,7 +574,7 @@ function BaseSourceControls() {
573574
<ColorInput
574575
label="Color"
575576
size="small"
576-
tooltip={`The hue and the saturation are taken; the tone is discarded, because the chrome's own lightness ladder is the design. A base color says which way the greys lean and how far, not how dark they are — and its saturation is clipped at ${MAX_BASE_SATURATION}, since a fully saturated chrome stops being chrome.`}
577+
tooltip={`Sets the hue and saturation of neutral UI colors such as surfaces, text, and borders. The color's lightness is ignored so the surface hierarchy stays intact, and saturation is capped at ${MAX_BASE_SATURATION} to keep the palette neutral.`}
577578
value={seedColor(input.base)}
578579
onChange={(base) =>
579580
// Same shape as the accent field above, and for the same reason: one seed
@@ -598,7 +599,7 @@ function BaseSourceControls() {
598599
<>
599600
<HueSlider
600601
label="Hue"
601-
tooltip="The neutral chrome — surface and its ladder, the surface-text ramp, border, placeholder. A colored theme's tinted surface deliberately follows its own hue instead, because a danger banner should read as red."
602+
tooltip="Sets the tint of neutral UI colors: surfaces, text, borders, and placeholders. Status surfaces keep their own semantic hues."
602603
value={Math.round(palette.baseHue)}
603604
onChange={(hue) =>
604605
setPalette((config) => ({
@@ -612,8 +613,8 @@ function BaseSourceControls() {
612613
label="Saturation"
613614
tooltip={
614615
palette.surfaceMode === 'tinted'
615-
? `The same 0–100 scale the accent saturation uses, on the chrome alone. The shipped value is 12 — a faint tint is what a neutral surface is — so the interesting range is the low end, and past about 25 the base colors run out of scale and converge.`
616-
: `Reaches surface-2…surface-4, the borders and the text ramp, but not the page surface: at the end of the tone scale there is no room for chroma. Switch Surfaces to Tinted to give it some.`
616+
? 'Controls how strongly the base hue tints neutral UI colors. Values above about 25 produce little visible change because the base palette intentionally stays near-neutral.'
617+
: 'Tints elevated surfaces, borders, and text, but not the page background. Choose Tinted surfaces to make the base hue visible on the page background too.'
617618
}
618619
// The clip a derived base color gets, so the manual and the derived
619620
// routes agree on what the top of the range means. `surface-inverse`
@@ -762,21 +763,21 @@ function PaletteModeTabs() {
762763
<Radio
763764
value="pastel"
764765
styles={MODE_TAB_STYLES}
765-
tooltip="One flat, hue-independent chroma ceiling. It is what makes the palette even across hues, and it leaves nothing for a saturation scale to do — so there are no saturation sliders here except the syntax one, which pastel never reaches."
766+
tooltip="Uses one restrained color-intensity limit across all hues for a softer, more even palette. Saturation controls are hidden because this mode manages intensity automatically; code colors remain independent."
766767
>
767768
Pastel
768769
</Radio>
769770
<Radio
770771
value="advanced"
771772
styles={MODE_TAB_STYLES}
772-
tooltip="The per-hue ceiling, with a hue and a saturation on each zone. The same space Color uses; this is the half where you dial the numbers yourself."
773+
tooltip="Lets you edit hue and saturation numerically for each palette area. It uses the full color-intensity range and is best when you want precise control."
773774
>
774775
Advanced
775776
</Radio>
776777
<Radio
777778
value="color"
778779
styles={MODE_TAB_STYLES}
779-
tooltip="The same space as Advanced, seeded from the hexes a brand usually arrives as — every zone, statuses included. An accent color contributes hue, chroma and tone; a base color contributes hue and saturation; a status color contributes all three and becomes its theme's chroma reference. Whatever a color supplies, its slider goes away."
780+
tooltip="Seeds the accent, base, and status themes with hex colors. Accent and status colors supply hue, intensity, and tone; the base color supplies hue and saturation. Equivalent sliders are hidden."
780781
>
781782
Color
782783
</Radio>
@@ -807,7 +808,7 @@ function GlobalControls() {
807808
labelPosition="split"
808809
type="button"
809810
value={palette.surfaceMode}
810-
tooltip="Tinted moves the whole surface ramp two tones off the end of the tone scale — the neutral surfaces, the status themes' tinted ones, and the mirrored surface the syntax palette solves against. Not a lightness change: chroma needs distance from the extreme to exist at all, so a neutral light page is white whatever the base saturation asks for. Two tones is the cheapest room in which the base hue becomes visible."
811+
tooltip="Neutral keeps the page background at the end of the tone scale. Tinted shifts the surface ramp slightly inward, giving the base hue room to appear across neutral and status surfaces."
811812
onChange={(surfaceMode) =>
812813
setPalette((config) => ({
813814
...config,
@@ -964,8 +965,8 @@ function ContrastControls() {
964965
label="Contrast level"
965966
tooltip={
966967
hasContrastTier()
967-
? 'The level moves the normal colors only. The high-contrast tier is the true high-contrast resolution at every level, so the two compose — a contrast preference still escalates on top of wherever the slider puts the baseline.'
968-
: 'One tier at level 100: the normal colors already are the high-contrast ones here, so data-contrast="high" and prefers-contrast: more have nothing left to escalate to. Every level below this keeps both tiers.'
968+
? 'Raises the normal palette from its shipped contrast at 0 toward the high-contrast palette at 100. The separate high-contrast tier remains available until the level reaches 100.'
969+
: 'At 100, the normal palette already matches the high-contrast palette, so only one tier is generated. Lower the level to preview normal and high contrast separately.'
969970
}
970971
value={level}
971972
onChange={(contrastLevel) =>
@@ -1524,7 +1525,7 @@ function StatusThemeButton({
15241525
theme="current"
15251526
size="small"
15261527
color={`#${name}-text`}
1527-
tooltip={`Tune the ${name} theme — currently ${Math.round(seed.hue)}°`}
1528+
tooltip={`Edit the ${name} theme. Current hue: ${Math.round(seed.hue)}°.`}
15281529
icon={
15291530
<ColorSwatch
15301531
color={tokens[`#${name}-accent-surface`] as string | undefined}
@@ -1542,7 +1543,7 @@ function StatusThemeButton({
15421543
<ColorInput
15431544
label="Color"
15441545
size="small"
1545-
tooltip="Hue, chroma and tone all come from here. The fill becomes this color on the same terms the brand does — reproduced in light at normal contrast, adapting in dark and high contrast, and held above APCA Lc 45 against the white label it carries and Lc 25 against the page. Its chroma also becomes the theme's own scale, which is what keeps the tinted banner, the border and the text ramp in the proportion to the fill that they have on every other theme."
1546+
tooltip="Sets this status theme's hue, intensity, and tone from one hex color. The light, normal-contrast fill matches it when accessibility limits allow; dark and high-contrast variants adapt automatically. The color also seeds this theme's surfaces, borders, and text."
15461547
value={seedColor(written)}
15471548
onChange={(color) =>
15481549
// Clearing lands back on a hue pinned where the color left it. There is
@@ -1558,7 +1559,7 @@ function StatusThemeButton({
15581559
) : (
15591560
<HueSlider
15601561
label="Hue"
1561-
tooltip="Status hues have to stay semantically legible — danger red, warning amber, success green — and about 35° apart from each other and from the brand, which is roughly where two tinted surfaces stop reading as one color."
1562+
tooltip="Sets this status hue. Keep danger red, warning amber, and success green; as a starting point, separate status and brand hues by about 35° so tinted surfaces remain distinguishable."
15621563
value={Math.round(seed.hue)}
15631564
onChange={(hue) =>
15641565
setPalette(
@@ -1588,14 +1589,14 @@ function StatusThemeButton({
15881589
<Token>Saturation {pinnedSaturation} — pinned</Token>
15891590
<InfoBadge
15901591
theme="danger"
1591-
tooltip={`Set while pastel was off, and still in effect. Pastel's flat ceiling is what governs status chroma now, so there is no scale to offer on top of it — turn pastel off to reach this number again, or to clear it.`}
1592+
tooltip="This value was customized before Pastel was enabled and still affects the theme. Switch to Advanced to change or clear it."
15921593
/>
15931594
</Row>
15941595
) : null
15951596
) : (
15961597
<Slider
15971598
label="Saturation"
1598-
tooltip="Inherits the palette saturation until you move it, and stays pinned afterwards — so a re-seeded palette leaves this theme where you put it."
1599+
tooltip="Controls this status theme's intensity. It initially follows Accent saturation; once changed, it stays independent of later accent changes."
15991600
value={seed.saturation}
16001601
onChange={(saturation) =>
16011602
setPalette(
@@ -1656,7 +1657,11 @@ function ExportButton() {
16561657
mobileType="tray"
16571658
placement="bottom start"
16581659
>
1659-
<Button size="small" icon={<CopyIcon />}>
1660+
<Button
1661+
size="small"
1662+
icon={<CopyIcon />}
1663+
tooltip="Open a copyable setPaletteConfig(...) snippet."
1664+
>
16601665
Export
16611666
</Button>
16621667
<Dialog aria-label="Export the palette config" width="max-content">
@@ -1689,7 +1694,7 @@ function DownloadButton() {
16891694
<Button
16901695
size="small"
16911696
icon={<IconDownload />}
1692-
tooltip="Download the config as palette.json"
1697+
tooltip="Download the sparse palette config as palette.json."
16931698
onPress={download}
16941699
>
16951700
JSON
@@ -1710,7 +1715,7 @@ function BuilderActions() {
17101715
<Button
17111716
size="small"
17121717
icon={<ReloadIcon />}
1713-
tooltip="Discard every change and return to the shipped palette"
1718+
tooltip="Discard all changes and restore the shipped palette."
17141719
onPress={resetPaletteConfig}
17151720
>
17161721
Reset
@@ -1721,6 +1726,14 @@ function BuilderActions() {
17211726
);
17221727
}
17231728

1729+
const PresetGrid = tasty({
1730+
styles: {
1731+
display: 'grid',
1732+
gridColumns: 'repeat(3, 1fr)',
1733+
gap: '1x',
1734+
},
1735+
});
1736+
17241737
function ThemeBuilderControls({
17251738
tokens,
17261739
documentTokens,
@@ -1743,11 +1756,12 @@ function ThemeBuilderControls({
17431756

17441757
<ControlGroup>
17451758
<GroupLabel>Presets</GroupLabel>
1746-
<Row>
1759+
<PresetGrid>
17471760
{THEME_PRESETS.map((preset) => (
17481761
<Button
17491762
key={preset.label}
17501763
size="small"
1764+
width="100%"
17511765
// The default `outline` shows selection as a filled chip, so the
17521766
// active preset reads as the state it is rather than as a fifth style.
17531767
isSelected={activePreset === preset.label}
@@ -1760,7 +1774,7 @@ function ThemeBuilderControls({
17601774
{preset.label}
17611775
</Button>
17621776
))}
1763-
</Row>
1777+
</PresetGrid>
17641778
</ControlGroup>
17651779

17661780
{/* Ahead of the zones, because everything in here governs both of them —
@@ -1800,7 +1814,7 @@ function ThemeBuilderControls({
18001814
<GroupLabel>Syntax</GroupLabel>
18011815
<Slider
18021816
label="Code saturation"
1803-
tooltip="Hues are fixed; only saturation is tunable. The syntax family carries absolute hues and its own seed, so neither the brand hue nor the palette saturation reaches it — a brand re-seeded toward green would otherwise collide strings with numbers."
1817+
tooltip="Controls only the intensity of syntax colors. Their hues are fixed and independent of the brand so code categories stay distinct."
18041818
value={palette.themes.code.saturation}
18051819
onChange={(saturation) =>
18061820
setPalette((config) => ({
@@ -2222,13 +2236,11 @@ function ThemeBuilderPage() {
22222236
title="Theme builder"
22232237
description={
22242238
<>
2225-
Every control on the left writes to the live palette config; the panel
2226-
on the right renders it into a single region through a tasty{' '}
2227-
<Token>tokens</Token> prop. The palette is generated by{' '}
2228-
<Link to="!https://github.com/tenphi/glaze">Glaze</Link>. The two
2229-
switches over the preview pick which of the theme&rsquo;s four
2230-
variants to show — they start on whatever this page is already in, and
2231-
change nothing about the theme itself.
2239+
Build a palette with the controls and watch the preview update
2240+
instantly. Use the preview switches to check light, dark, normal, and
2241+
high-contrast variants without changing the palette, then export the
2242+
result as code or JSON. Colors are generated by{' '}
2243+
<Link to="!https://github.com/tenphi/glaze">Glaze</Link>.
22322244
</>
22332245
}
22342246
>
@@ -2264,7 +2276,7 @@ function ThemeBuilderPage() {
22642276
{hasContrastTier() ? null : (
22652277
<InfoBadge
22662278
theme="danger"
2267-
tooltip="No separate tier at contrast level 100 — the normal colors already are the high-contrast ones, so there is nothing to escalate to and the preview is the normal variant."
2279+
tooltip="At level 100, the normal palette already matches the high-contrast palette, so there is no separate variant to preview. Lower the Contrast level to restore both options."
22682280
/>
22692281
)}
22702282
</PreviewToolbar>

0 commit comments

Comments
 (0)