From f5f040516e1e7ab5ff8c35c015541dd19c2385c5 Mon Sep 17 00:00:00 2001 From: Andreas Reinhold / reini Date: Tue, 15 Sep 2026 12:41:20 +0200 Subject: [PATCH] Smooth the page's text in the foundation, not per surface The showcase and the error pages set grayscale font smoothing on themselves, but the foundation did not, so an application that dropped Tailwind's antialiased class rendered heavier text on macOS than the package's own pages, silently. base.css now sets it on the page; the showcase's and error page's copies go, and the guard tells a leftover antialiased class to go too. Decided with the user (plan step 46). Co-Authored-By: Claude Opus 5 (1M context) --- UPGRADE.md | 3 ++- .../skills/livewire-material-development/SKILL.md | 2 +- resources/css/components/error-page.css | 1 - resources/css/foundation/base.css | 7 +++++++ resources/css/showcase.css | 5 ----- src/Testing/DesignGuard.php | 3 ++- tests/Feature/DesignGuardTest.php | 1 + tests/Feature/ShowcaseAssetsTest.php | 2 +- tests/Feature/StylesheetsTest.php | 10 ++++++++++ .../Fixtures/design-guard/tailwind/utilities.blade.php | 2 +- 10 files changed, 25 insertions(+), 11 deletions(-) diff --git a/UPGRADE.md b/UPGRADE.md index 79d737c4..881cfc6c 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -138,7 +138,8 @@ component the views render, opening with the layer statement every package style either: layout components (``, ``, ``, ``, ``, the canonical layouts) take M3's spacing tokens and breakpoints as props, a small set of `md-type-*` and `md-ink-*` classes covers text on plain elements, and `--md-sys-*` custom properties serve the -rest of an application's own stylesheet. +rest of an application's own stylesheet. The foundation smooths text in grayscale, as Tailwind's +`antialiased` class did: drop the class. ### 7. New in 2.0.0 diff --git a/resources/boost/skills/livewire-material-development/SKILL.md b/resources/boost/skills/livewire-material-development/SKILL.md index 256258bf..80431369 100644 --- a/resources/boost/skills/livewire-material-development/SKILL.md +++ b/resources/boost/skills/livewire-material-development/SKILL.md @@ -90,7 +90,7 @@ Scheme::resolveProfileUsing(fn (): ?string => Setting::get('color_profile')); ## Tokens -2.0.0's vocabulary is plain CSS, without Tailwind. `resources/css/foundation.css` is the one required import, first in the entry, followed by the stylesheets of the components the views render (see Setup; 1.x's `material.css` shortcut is gone). It declares the layer order `material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility`, so an application's unlayered CSS beats every package rule, and `hidden` or `x-cloak` hides any element. It brings the reset, every `--md-sys-*` token (spacing included: `--md-sys-measurement-space25` … `space900`, 2–72px), and `md-state-layer`, `md-focus-ring`, `md-touch-target`, `md-link`. Text on plain elements takes the fixed text classes of `resources/css/text.css` and nothing else: `md-type-{display|headline|title|body|label}-{lg|md|sm}` and `md-type-emphasized-…`; `md-ink` (on-surface), `md-ink-variant`, `md-ink-quiet`, `md-ink-primary`, `md-ink-error`, `md-ink-success`, `md-ink-warning`, `md-ink-info`, `md-ink-inverse`; `md-text-start|center|end`, `md-truncate`, `md-line-clamp-2|3`, `md-nowrap`, `md-tabular`, `md-visually-hidden`. Every component is plain CSS now; the Tailwind names below stay for the showcase and an application's own views, until Tailwind itself leaves the package. +2.0.0's vocabulary is plain CSS, without Tailwind. `resources/css/foundation.css` is the one required import, first in the entry, followed by the stylesheets of the components the views render (see Setup; 1.x's `material.css` shortcut is gone). It declares the layer order `material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility`, so an application's unlayered CSS beats every package rule, and `hidden` or `x-cloak` hides any element. It brings the reset, the page's `surface` and `on-surface` in the brand typeface smoothed in grayscale (drop Tailwind's `antialiased`), every `--md-sys-*` token (spacing included: `--md-sys-measurement-space25` … `space900`, 2–72px), and `md-state-layer`, `md-focus-ring`, `md-touch-target`, `md-link`. Text on plain elements takes the fixed text classes of `resources/css/text.css` and nothing else: `md-type-{display|headline|title|body|label}-{lg|md|sm}` and `md-type-emphasized-…`; `md-ink` (on-surface), `md-ink-variant`, `md-ink-quiet`, `md-ink-primary`, `md-ink-error`, `md-ink-success`, `md-ink-warning`, `md-ink-info`, `md-ink-inverse`; `md-text-start|center|end`, `md-truncate`, `md-line-clamp-2|3`, `md-nowrap`, `md-tabular`, `md-visually-hidden`. Every component is plain CSS now; the Tailwind names below stay for the showcase and an application's own views, until Tailwind itself leaves the package. Tailwind's default palette is cleared: every colour class names an M3 role. `text-red-600`, `bg-base-200` or `text-gray-500` compile to nothing. The rules behind the names below — which role, surface container, corner, type style, elevation level, motion spring, state and window size class to use, and M3's don'ts — are the `material-3` guideline (always on) and the `material-3-design` skill (the tables and Google's source pages); activate that skill before designing a screen. diff --git a/resources/css/components/error-page.css b/resources/css/components/error-page.css index 982f4600..c43ab10e 100644 --- a/resources/css/components/error-page.css +++ b/resources/css/components/error-page.css @@ -48,7 +48,6 @@ min-height: 100dvh; background-color: var(--md-sys-color-surface); color: var(--md-sys-color-on-surface); - -webkit-font-smoothing: antialiased; } [data-md-error-page] { diff --git a/resources/css/foundation/base.css b/resources/css/foundation/base.css index bda12bcc..bbb36fd7 100644 --- a/resources/css/foundation/base.css +++ b/resources/css/foundation/base.css @@ -8,6 +8,11 @@ * `--md-ref-typeface-brand` (tokens/type.css), Google Sans Flex from tokens/font.css, whose * `@font-face` this file imports into the same layer. * + * Text is smoothed in grayscale where a platform offers the choice (macOS browsers): Google Sans + * Flex is drawn for it, and subpixel smoothing thickens its strokes on light text over a dark + * surface. Every surface the package draws — the showcase, the error pages — is set this way, + * and so is an application, which drops Tailwind's `antialiased` class with nothing to replace. + * * In `material.base`, above the reset and below every component and text class. */ @@ -20,5 +25,7 @@ background-color: var(--md-sys-color-surface); color: var(--md-sys-color-on-surface); font-family: var(--md-ref-typeface-brand); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; } } diff --git a/resources/css/showcase.css b/resources/css/showcase.css index 22819aab..d1294db3 100644 --- a/resources/css/showcase.css +++ b/resources/css/showcase.css @@ -28,11 +28,6 @@ @layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility; @layer material.components { - [data-md-showcase] { - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - } - /* The rail's wordmark: `md-type-title-lg md-truncate md-focus-ring` cover the type, the ellipsis and the ring; `display: block` gives the truncated text a width to ellipsize within, and the corner rounds the ring to match the state layer every other rail control diff --git a/src/Testing/DesignGuard.php b/src/Testing/DesignGuard.php index c67f9cbd..3fe109ce 100644 --- a/src/Testing/DesignGuard.php +++ b/src/Testing/DesignGuard.php @@ -161,8 +161,9 @@ class DesignGuard ['/^(?:cursor-[\w-]+|pointer-events-(?:none|auto)|select-(?:none|text|all|auto)|touch-[\w-]+|resize(?:-[xy]|-none)?|appearance-(?:none|auto)|scroll-(?:smooth|auto)|-?scroll-[mp][trblxyse]?-[\w.]+|snap-[\w-]+)$/', 'Tailwind interactivity utility `%s` compiles to nothing — write the rule in your own CSS'], ['/^(?:aspect-(?:auto|square|video|\d+\/\d+)|object-(?:contain|cover|fill|none|scale-down|top|bottom|center|left|right|left-top|left-bottom|right-top|right-bottom))$/', 'Tailwind media utility `%s` compiles to nothing — write `aspect-ratio`/`object-fit` in your own CSS'], ['/^font-(?:sans|serif)$/', 'Tailwind\'s `%s` compiles to nothing — the foundation already sets the brand typeface; any other `font-family` is your own CSS'], + ['/^antialiased$/', 'Tailwind\'s `%s` compiles to nothing — the foundation already smooths text in grayscale (base.css); drop it'], ['/^(?:underline|no-underline)$/', 'Tailwind\'s `%s` compiles to nothing — `md-link` draws a link (interaction.css); any other decoration is your own CSS'], - ['/^(?:uppercase|lowercase|capitalize|normal-case|italic|not-italic|overline|line-through|underline-offset-\w+|decoration-(?:\d+|solid|double|dotted|dashed|wavy|auto|from-font|clone|slice)|font-mono|antialiased|subpixel-antialiased|whitespace-(?:normal|pre|pre-line|pre-wrap|break-spaces)|break-(?:words|all|keep|normal)|wrap-(?:break-word|anywhere|normal)|text-(?:wrap|balance|pretty|ellipsis|clip|justify)|line-clamp-(?:\d+|none)|not-sr-only|list-(?:disc|decimal|none|inside|outside)|align-(?:baseline|top|middle|bottom|text-top|text-bottom|sub|super)|indent-[\w.]+|hyphens-(?:none|manual|auto)|(?:normal|lining|oldstyle|proportional)-nums|ordinal|slashed-zero|(?:diagonal|stacked)-fractions)$/', 'Tailwind text utility `%s` compiles to nothing — write the rule in your own CSS'], + ['/^(?:uppercase|lowercase|capitalize|normal-case|italic|not-italic|overline|line-through|underline-offset-\w+|decoration-(?:\d+|solid|double|dotted|dashed|wavy|auto|from-font|clone|slice)|font-mono|subpixel-antialiased|whitespace-(?:normal|pre|pre-line|pre-wrap|break-spaces)|break-(?:words|all|keep|normal)|wrap-(?:break-word|anywhere|normal)|text-(?:wrap|balance|pretty|ellipsis|clip|justify)|line-clamp-(?:\d+|none)|not-sr-only|list-(?:disc|decimal|none|inside|outside)|align-(?:baseline|top|middle|bottom|text-top|text-bottom|sub|super)|indent-[\w.]+|hyphens-(?:none|manual|auto)|(?:normal|lining|oldstyle|proportional)-nums|ordinal|slashed-zero|(?:diagonal|stacked)-fractions)$/', 'Tailwind text utility `%s` compiles to nothing — write the rule in your own CSS'], ['/^bg-(?:(?:gradient-to|linear-to)-[a-z]+|radial|conic|cover|contain|auto|center|top|bottom|left|right|no-repeat|repeat(?:-[xy])?|fixed|local|scroll|none|clip-[a-z]+|origin-[a-z]+)$/', 'Tailwind background utility `%s` compiles to nothing — write the rule in your own CSS'], ]; diff --git a/tests/Feature/DesignGuardTest.php b/tests/Feature/DesignGuardTest.php index 26562d2a..39cd7f49 100644 --- a/tests/Feature/DesignGuardTest.php +++ b/tests/Feature/DesignGuardTest.php @@ -372,6 +372,7 @@ it('reports position, border, effect, interactivity and text utilities with the 'tailwind/utilities.blade.php:3 Tailwind text utility `font-mono` compiles to nothing — write the rule in your own CSS', 'tailwind/utilities.blade.php:3 Tailwind text utility `list-disc` compiles to nothing — write the rule in your own CSS', 'tailwind/utilities.blade.php:3 Tailwind text utility `uppercase` compiles to nothing — write the rule in your own CSS', + "tailwind/utilities.blade.php:3 Tailwind's `antialiased` compiles to nothing — the foundation already smooths text in grayscale (base.css); drop it", "tailwind/utilities.blade.php:3 Tailwind's `font-sans` compiles to nothing — the foundation already sets the brand typeface; any other `font-family` is your own CSS", "tailwind/utilities.blade.php:3 Tailwind's `text-end` compiles to nothing — use `md-text-end` (text.css)", "tailwind/utilities.blade.php:3 Tailwind's `underline` compiles to nothing — `md-link` draws a link (interaction.css); any other decoration is your own CSS", diff --git a/tests/Feature/ShowcaseAssetsTest.php b/tests/Feature/ShowcaseAssetsTest.php index 2b3ee7a8..b1ab68a0 100644 --- a/tests/Feature/ShowcaseAssetsTest.php +++ b/tests/Feature/ShowcaseAssetsTest.php @@ -21,7 +21,7 @@ it('serves the bundle as long-cached CSS at the hash of its own content', functi ->assertHeader('Cache-Control', 'immutable, max-age=31536000, public'); expect($response->getContent()) - ->toContain('[data-md-showcase]') + ->toContain('[data-md-showcase-brand]') ->toContain('[data-md-pane]') ->toContain('@layer material.reset, material.tokens'); }); diff --git a/tests/Feature/StylesheetsTest.php b/tests/Feature/StylesheetsTest.php index dffe1a27..3126f740 100644 --- a/tests/Feature/StylesheetsTest.php +++ b/tests/Feature/StylesheetsTest.php @@ -212,6 +212,16 @@ it('brings every foundation file and text.css into the foundation, and nothing e ->and(array_values(preg_grep('/^components\//', $foundation)))->toBe([]); }); +it('paints the page in surface and on-surface, in the brand typeface, smoothed in grayscale', function () { + // An application drops Tailwind's `antialiased` with nothing to replace it: the showcase and the + // error pages, which set it themselves before, read it from here too. + $base = (string) preg_replace('~/\*.*?\*/~s', '', (string) file_get_contents(stylesheetPath('foundation/base.css'))); + + expect($base)->toMatch('/html \{\s*background-color: var\(--md-sys-color-surface\);\s*color: var\(--md-sys-color-on-surface\);\s*font-family: var\(--md-ref-typeface-brand\);\s*-webkit-font-smoothing: antialiased;\s*-moz-osx-font-smoothing: grayscale;\s*\}/') + ->and((string) file_get_contents(stylesheetPath('showcase.css')).file_get_contents(stylesheetPath('components/error-page.css'))) + ->not->toContain('font-smoothing'); +}); + it('leaves no stylesheet under resources/css/ outside all.css or showcase.css', function () { // The full tree now that Tailwind is gone (plan step 39): every .css file on disk, not just the // ones a bundler happens to reach — a stray or orphaned file would show up here even though diff --git a/tests/Fixtures/design-guard/tailwind/utilities.blade.php b/tests/Fixtures/design-guard/tailwind/utilities.blade.php index 8ba2ad1d..ce9a4dd2 100644 --- a/tests/Fixtures/design-guard/tailwind/utilities.blade.php +++ b/tests/Fixtures/design-guard/tailwind/utilities.blade.php @@ -1,4 +1,4 @@
- +