From c92800dedbbd175ea15b9c40001760c89a880d04 Mon Sep 17 00:00:00 2001 From: Antoine BERNIER Date: Tue, 6 Oct 2026 12:15:03 +0200 Subject: [PATCH 1/2] feat: write each poster label in its paired M3 role, as Material Theme Builder does `primary` reads in `on-primary`, `on-primary` in `primary`, the surface family in `on-surface`, `outline` in `surface`, `inverse-primary` in `on-surface`, `scrim` and `shadow` in plain white; tones 80 and up in black, the rest in white -- the way the official app's poster paints them, instead of white in `mix-blend-mode: difference`. The fixed accents, which that poster does not draw, follow the same pairing, as do the custom colors and the dropped roles. Co-Authored-By: Claude Fable 5.1 --- .changeset/poster-label-ink.md | 5 + src/Scheme.tsx | 187 +++++++++++++++++++++++---------- 2 files changed, 136 insertions(+), 56 deletions(-) create mode 100644 .changeset/poster-label-ink.md diff --git a/.changeset/poster-label-ink.md b/.changeset/poster-label-ink.md new file mode 100644 index 00000000..7b8ac947 --- /dev/null +++ b/.changeset/poster-label-ink.md @@ -0,0 +1,5 @@ +--- +"material-theme-builder": patch +--- + +Write each poster label in the M3 role it is paired with -- `primary` in `on-primary`, `on-primary` in `primary`, the surface family in `on-surface`, tones 80 and up in black and the rest in white -- as Material Theme Builder's poster does, instead of white in `mix-blend-mode: difference`. diff --git a/src/Scheme.tsx b/src/Scheme.tsx index 82b9d809..da8b6860 100644 --- a/src/Scheme.tsx +++ b/src/Scheme.tsx @@ -173,10 +173,82 @@ const twClasses = { surfaceTint: "bg-surface-tint", } satisfies Record; +/** + * The role each swatch's label is written in -- its M3 pair, the way the + * official app's poster does it: `primary` reads in `on-primary` and + * `on-primary` in `primary`; the surface family reads in `on-surface`, and + * the inks (`on-surface`, `outline`) in `surface`. `scrim` and `shadow` are + * black in both schemes, so their label is plain white, as in the app. + * + * The fixed accents are not on the app's poster; they follow the same pairing. + */ +const ink = { + primary: "onPrimary", + onPrimary: "primary", + primaryContainer: "onPrimaryContainer", + onPrimaryContainer: "primaryContainer", + secondary: "onSecondary", + onSecondary: "secondary", + secondaryContainer: "onSecondaryContainer", + onSecondaryContainer: "secondaryContainer", + tertiary: "onTertiary", + onTertiary: "tertiary", + tertiaryContainer: "onTertiaryContainer", + onTertiaryContainer: "tertiaryContainer", + + error: "onError", + onError: "error", + errorContainer: "onErrorContainer", + onErrorContainer: "errorContainer", + + primaryFixed: "onPrimaryFixed", + primaryFixedDim: "onPrimaryFixed", + onPrimaryFixed: "primaryFixed", + onPrimaryFixedVariant: "primaryFixed", + secondaryFixed: "onSecondaryFixed", + secondaryFixedDim: "onSecondaryFixed", + onSecondaryFixed: "secondaryFixed", + onSecondaryFixedVariant: "secondaryFixed", + tertiaryFixed: "onTertiaryFixed", + tertiaryFixedDim: "onTertiaryFixed", + onTertiaryFixed: "tertiaryFixed", + onTertiaryFixedVariant: "tertiaryFixed", + + surfaceDim: "onSurface", + surface: "onSurface", + surfaceBright: "onSurface", + surfaceContainerLowest: "onSurface", + surfaceContainerLow: "onSurface", + surfaceContainer: "onSurface", + surfaceContainerHigh: "onSurface", + surfaceContainerHighest: "onSurface", + onSurface: "surface", + onSurfaceVariant: "surfaceVariant", + outline: "surface", + outlineVariant: "onSurface", + + inverseSurface: "inverseOnSurface", + inverseOnSurface: "inverseSurface", + inversePrimary: "onSurface", + scrim: "white", + shadow: "white", + + background: "onBackground", + onBackground: "background", + surfaceVariant: "onSurfaceVariant", + surfaceTint: "onPrimary", +} satisfies Record; + +/** The CSS color a label is written in, from the role it is paired with. */ +function inkOf(role: TokenName | "white"): string { + return role === "white" ? "white" : `var(--md-sys-color-${kebabCase(role)})`; +} + /** * One color cell: the role as `title`, its human name as label, the color * itself from `var(--md-sys-color-)` — or from the Tailwind utility when - * under a `tw` `Scheme`. + * under a `tw` `Scheme`. The label is written in the role's pair (`ink`), + * always from the var: `tw` is about the `bg-*` utilities resolving. */ function Swatch({ role, @@ -195,11 +267,11 @@ function Swatch({
{children ??

{startCase(role)}

} @@ -235,8 +307,6 @@ function PosterStyle({ notext }: { notext?: boolean }) { p { font-family: sans-serif; - color: white; - mix-blend-mode: difference; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; @@ -292,6 +362,34 @@ const themeStyles = { }, } satisfies Record<"light" | "dark", CSSProperties>; +/** + * One cell of a custom color's row: the `--md-sys-color-` token as its + * color, its `label` written in the `ink` token -- its pair, as `ink` maps + * the standard roles. + */ +function CustomSwatch({ + name, + ink, + label, +}: { + name: string; + ink: string; + label: string; +}) { + return ( +
+

{label}

+
+ ); +} + /** * The four roles the current spec no longer lists, as an extra row under * `on-surface` — `background`, `on-background`, `surface-variant` and @@ -644,54 +742,30 @@ export function Scheme({ } {customColors && customColors.length > 0 && (
- {customColors?.map((customColor) => ( -
- - -

{upperFirst(customColor.name)}

-
-
- - -

On {upperFirst(customColor.name)}

-
-
- - -

{upperFirst(customColor.name)} Container

-
-
- - -

On {upperFirst(customColor.name)} Container

-
-
-
- ))} + {customColors.map((customColor) => { + const name = kebabCase(customColor.name); + const label = upperFirst(customColor.name); + return ( +
+ + + + +
+ ); + })}
)} @@ -753,6 +827,7 @@ export function Shades({ alignItems: "center", justifyContent: "center", backgroundColor: `var(--md-ref-palette-${isCustom ? kebabCase(name) : name}-${tone})`, + color: tone >= 80 ? "black" : "white", }} >

{tone}

From cf936ed242959cc9a4e2929cc293c780e00be258 Mon Sep 17 00:00:00 2001 From: Antoine BERNIER Date: Tue, 6 Oct 2026 12:34:52 +0200 Subject: [PATCH 2/2] feat: pair the Tailwind story's hand-written tiles the same way The Tailwind story draws its custom-color tiles and tonal shades by hand, as literal utility classes, outside `Scheme` and `Shades` -- so the pairing did not reach them. Each tile now reads in its pair (`text-on-myCustomColor1`), and shades 50 to 200 (tones 95 to 80) in black, the rest in white. Co-Authored-By: Claude Fable 5.1 --- src/Mtb.stories.helpers.tsx | 198 +++++++++++++++++++----------------- 1 file changed, 102 insertions(+), 96 deletions(-) diff --git a/src/Mtb.stories.helpers.tsx b/src/Mtb.stories.helpers.tsx index 492571af..08954e4a 100644 --- a/src/Mtb.stories.helpers.tsx +++ b/src/Mtb.stories.helpers.tsx @@ -104,46 +104,52 @@ function TailwindCustomColors() { return (
-
+

MyCustomColor1

On MyCustomColor1

MyCustomColor1 Container

On MyCustomColor1 Container

-
+

MyCustomColor2

On MyCustomColor2

MyCustomColor2 Container

On MyCustomColor2 Container

@@ -195,37 +201,37 @@ export function TailwindScheme() {

Primary

-
+
50
-
+
100
-
+
200
-
+
300
-
+
400
-
+
500
-
+
600
-
+
700
-
+
800
-
+
900
-
+
950
@@ -235,37 +241,37 @@ export function TailwindScheme() {

Secondary

-
+
50
-
+
100
-
+
200
-
+
300
-
+
400
-
+
500
-
+
600
-
+
700
-
+
800
-
+
900
-
+
950
@@ -275,37 +281,37 @@ export function TailwindScheme() {

Tertiary

-
+
50
-
+
100
-
+
200
-
+
300
-
+
400
-
+
500
-
+
600
-
+
700
-
+
800
-
+
900
-
+
950
@@ -315,37 +321,37 @@ export function TailwindScheme() {

Error

-
+
50
-
+
100
-
+
200
-
+
300
-
+
400
-
+
500
-
+
600
-
+
700
-
+
800
-
+
900
-
+
950
@@ -355,37 +361,37 @@ export function TailwindScheme() {

Neutral

-
+
50
-
+
100
-
+
200
-
+
300
-
+
400
-
+
500
-
+
600
-
+
700
-
+
800
-
+
900
-
+
950
@@ -395,37 +401,37 @@ export function TailwindScheme() {

Neutral Variant

-
+
50
-
+
100
-
+
200
-
+
300
-
+
400
-
+
500
-
+
600
-
+
700
-
+
800
-
+
900
-
+
950
@@ -435,37 +441,37 @@ export function TailwindScheme() {

myCustomColor1

-
+
50
-
+
100
-
+
200
-
+
300
-
+
400
-
+
500
-
+
600
-
+
700
-
+
800
-
+
900
-
+
950
@@ -475,37 +481,37 @@ export function TailwindScheme() {

myCustomColor2

-
+
50
-
+
100
-
+
200
-
+
300
-
+
400
-
+
500
-
+
600
-
+
700
-
+
800
-
+
900
-
+
950