the empty size option names the size

"Inherit (default)" answers a question nobody asked. What somebody wants from
that dropdown is what it is now — and a label that will not say makes them change
the setting to find out, which is the one way to lose the answer.

It reads Default (4xl) at the smallest width and Inherit (2xl) above it. The
stylesheet's own sizes come from the API, which reads them out of styles.css.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-01 08:48:51 +01:00
co-authored by Claude Opus 5
parent b165e10f53
commit aab536059b
2 changed files with 21 additions and 6 deletions
+18 -6
View File
@@ -13,8 +13,16 @@ import { ROLES } from './roles';
* ── One value per field ──
*
* The tabs at the top of the panel say which width. Unset inherits the smaller one, so "the same
* everywhere" is one choice; the option names what it is inheriting rather than leaving it to be
* worked out.
* everywhere" is one choice.
*
* ── The empty option NAMES the size ──
*
* It said "Inherit (default)", which answers a question nobody asked. What somebody wants from that
* dropdown is "what is it now", and a label that does not say makes them change the setting to find
* out — which is the one way to lose the answer.
*
* So it names the size that will actually apply: the nearest smaller width that has one, or the
* stylesheet's own, which the API reads out of styles.css rather than this file guessing.
*/
export type FontSizes = Partial<Record<'title' | 'body' | 'sub' | 'display', Partial<Record<'base' | 'md' | 'lg', string>>>>;
@@ -36,11 +44,14 @@ const inherited = (
export const FontSizesTab = ({
sizes,
steps,
defaults,
onChange,
}: {
sizes: FontSizes;
/** The scale, sent by the API so this file holds no copy of it. */
steps: string[];
/** What each role is with nothing chosen, read from the site's stylesheet. */
defaults: Record<string, string>;
onChange: (sizes: FontSizes) => void;
}) => {
const [device] = useDevice();
@@ -64,7 +75,8 @@ export const FontSizesTab = ({
{ROLES.map((role) => {
const current = sizes[role.size];
const own = current?.[key];
const from = inherited(current, key);
// The nearest smaller width that has one, or the stylesheet's. Either way, a size to read.
const from = inherited(current, key) ?? defaults[role.size];
return (
<div key={role.size} className="flex flex-col gap-1">
@@ -74,10 +86,10 @@ export const FontSizesTab = ({
onChange={(event) => set(role.size, event.currentTarget.value)}
className="h-8 rounded-md border border-input bg-transparent px-2 text-xs"
>
{/* Mobile is the smallest, so there is nothing below it to inherit from — and nothing
chosen there means the stylesheet's own size. */}
{/* Mobile is the smallest, so nothing below it: what it falls back to is the
stylesheet's own size rather than another breakpoint's. */}
<option value="">
{key === 'base' ? 'Default' : from === null ? 'Inherit (default)' : `Inherit (${from})`}
{from ? `${key === 'base' ? 'Default' : 'Inherit'} (${from})` : 'Default'}
</option>
{steps.map((step) => (
<option key={step} value={step}>
+3
View File
@@ -65,6 +65,8 @@ type Layout = {
font_sizes: FontSizes;
/** The type scale, sent by the API so this file holds no copy of it. */
text_steps: string[];
/** What each role is with nothing chosen, read from the site's stylesheet. */
size_defaults: Record<string, string>;
/** Each step's length, so the live preview can build the same CSS the site would. */
text_sizes: Record<string, string>;
/**
@@ -390,6 +392,7 @@ export const LayoutPanel = () => {
<FontSizesTab
sizes={draft.font_sizes}
steps={draft.text_steps}
defaults={draft.size_defaults}
onChange={(font_sizes) => patch({ font_sizes })}
/>
</AccordionContent>