You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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."
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 highcontrast 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 pageare adjusted."
473
473
value={seedColor(input.accent)}
474
474
onChange={(accent)=>
475
475
// Clearing the field is a change of path, so it lands back on a hue seed
// No value in the label: the slider already prints it on the right, and
498
498
// the same number twice on one line reads as two facts.
499
499
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."
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."
515
515
value={palette.saturation}
516
516
onChange={(saturation)=>
517
517
setPalette((config)=>({
@@ -547,6 +547,7 @@ function BaseSourceControls() {
547
547
labelPosition="split"
548
548
type="button"
549
549
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."
550
551
onChange={(next)=>
551
552
setPalette(({ base, ...config})=>
552
553
next==='own'
@@ -573,7 +574,7 @@ function BaseSourceControls() {
573
574
<ColorInput
574
575
label="Color"
575
576
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.`}
577
578
value={seedColor(input.base)}
578
579
onChange={(base)=>
579
580
// Same shape as the accent field above, and for the same reason: one seed
@@ -598,7 +599,7 @@ function BaseSourceControls() {
598
599
<>
599
600
<HueSlider
600
601
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."
602
603
value={Math.round(palette.baseHue)}
603
604
onChange={(hue)=>
604
605
setPalette((config)=>({
@@ -612,8 +613,8 @@ function BaseSourceControls() {
612
613
label="Saturation"
613
614
tooltip={
614
615
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.'
617
618
}
618
619
// The clip a derived base color gets, so the manual and the derived
619
620
// routes agree on what the top of the range means. `surface-inverse`
@@ -762,21 +763,21 @@ function PaletteModeTabs() {
762
763
<Radio
763
764
value="pastel"
764
765
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 huesfor a softer, more even palette. Saturation controls are hidden because this mode manages intensity automatically; code colors remain independent."
766
767
>
767
768
Pastel
768
769
</Radio>
769
770
<Radio
770
771
value="advanced"
771
772
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."
773
774
>
774
775
Advanced
775
776
</Radio>
776
777
<Radio
777
778
value="color"
778
779
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."
780
781
>
781
782
Color
782
783
</Radio>
@@ -807,7 +808,7 @@ function GlobalControls() {
807
808
labelPosition="split"
808
809
type="button"
809
810
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."
811
812
onChange={(surfaceMode)=>
812
813
setPalette((config)=>({
813
814
...config,
@@ -964,8 +965,8 @@ function ContrastControls() {
964
965
label="Contrast level"
965
966
tooltip={
966
967
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.'
969
970
}
970
971
value={level}
971
972
onChange={(contrastLevel)=>
@@ -1524,7 +1525,7 @@ function StatusThemeButton({
1524
1525
theme="current"
1525
1526
size="small"
1526
1527
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)}°.`}
@@ -1542,7 +1543,7 @@ function StatusThemeButton({
1542
1543
<ColorInput
1543
1544
label="Color"
1544
1545
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 highcontrast, 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."
1546
1547
value={seedColor(written)}
1547
1548
onChange={(color)=>
1548
1549
// Clearing lands back on a hue pinned where the color left it. There is
@@ -1558,7 +1559,7 @@ function StatusThemeButton({
1558
1559
) : (
1559
1560
<HueSlider
1560
1561
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."
1562
1563
value={Math.round(seed.hue)}
1563
1564
onChange={(hue)=>
1564
1565
setPalette(
@@ -1588,14 +1589,14 @@ function StatusThemeButton({
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."
1592
1593
/>
1593
1594
</Row>
1594
1595
) : null
1595
1596
) : (
1596
1597
<Slider
1597
1598
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."
1599
1600
value={seed.saturation}
1600
1601
onChange={(saturation)=>
1601
1602
setPalette(
@@ -1656,7 +1657,11 @@ function ExportButton() {
1656
1657
mobileType="tray"
1657
1658
placement="bottom start"
1658
1659
>
1659
-
<Buttonsize="small"icon={<CopyIcon/>}>
1660
+
<Button
1661
+
size="small"
1662
+
icon={<CopyIcon/>}
1663
+
tooltip="Open a copyable setPaletteConfig(...) snippet."
1664
+
>
1660
1665
Export
1661
1666
</Button>
1662
1667
<Dialogaria-label="Export the palette config"width="max-content">
@@ -1689,7 +1694,7 @@ function DownloadButton() {
1689
1694
<Button
1690
1695
size="small"
1691
1696
icon={<IconDownload/>}
1692
-
tooltip="Download the config as palette.json"
1697
+
tooltip="Download the sparse palette config as palette.json."
1693
1698
onPress={download}
1694
1699
>
1695
1700
JSON
@@ -1710,7 +1715,7 @@ function BuilderActions() {
1710
1715
<Button
1711
1716
size="small"
1712
1717
icon={<ReloadIcon/>}
1713
-
tooltip="Discard every change and return to the shipped palette"
1718
+
tooltip="Discard all changes and restore the shipped palette."
1714
1719
onPress={resetPaletteConfig}
1715
1720
>
1716
1721
Reset
@@ -1721,6 +1726,14 @@ function BuilderActions() {
1721
1726
);
1722
1727
}
1723
1728
1729
+
constPresetGrid=tasty({
1730
+
styles: {
1731
+
display: 'grid',
1732
+
gridColumns: 'repeat(3, 1fr)',
1733
+
gap: '1x',
1734
+
},
1735
+
});
1736
+
1724
1737
functionThemeBuilderControls({
1725
1738
tokens,
1726
1739
documentTokens,
@@ -1743,11 +1756,12 @@ function ThemeBuilderControls({
1743
1756
1744
1757
<ControlGroup>
1745
1758
<GroupLabel>Presets</GroupLabel>
1746
-
<Row>
1759
+
<PresetGrid>
1747
1760
{THEME_PRESETS.map((preset)=>(
1748
1761
<Button
1749
1762
key={preset.label}
1750
1763
size="small"
1764
+
width="100%"
1751
1765
// The default `outline` shows selection as a filled chip, so the
1752
1766
// active preset reads as the state it is rather than as a fifth style.
1753
1767
isSelected={activePreset===preset.label}
@@ -1760,7 +1774,7 @@ function ThemeBuilderControls({
1760
1774
{preset.label}
1761
1775
</Button>
1762
1776
))}
1763
-
</Row>
1777
+
</PresetGrid>
1764
1778
</ControlGroup>
1765
1779
1766
1780
{/* Ahead of the zones, because everything in here governs both of them —
@@ -1800,7 +1814,7 @@ function ThemeBuilderControls({
1800
1814
<GroupLabel>Syntax</GroupLabel>
1801
1815
<Slider
1802
1816
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."
1804
1818
value={palette.themes.code.saturation}
1805
1819
onChange={(saturation)=>
1806
1820
setPalette((config)=>({
@@ -2222,13 +2236,11 @@ function ThemeBuilderPage() {
2222
2236
title="Theme builder"
2223
2237
description={
2224
2238
<>
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
-
<Linkto="!https://github.com/tenphi/glaze">Glaze</Link>. The two
2229
-
switches over the preview pick which of the theme’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{' '}
@@ -2264,7 +2276,7 @@ function ThemeBuilderPage() {
2264
2276
{hasContrastTier() ? null : (
2265
2277
<InfoBadge
2266
2278
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."
0 commit comments