feat(utilities)!: close the utility gaps and name the sizing and underline families after their properties - #1044
Merged
Merged
Conversation
…rline families after their properties
Four families had no class in our build at all:
- `.border-x` / `.border-y` (and their `-0` forms) set the `border-inline` and
`border-block` shorthands, so a facing pair of borders takes one class
instead of two.
- `.min-w-{0|100}` and `.min-h-{0|100}` set a floor where only caps existed.
`.min-w-0` is the one that earns its place: a flex child starts at
`min-width: auto` and a `.text-truncate` child never breaks its line, so the
item pushes past its track instead of ellipsizing until the floor drops to 0.
- `.animation-{shake|pop}` ship their keyframes with them. The keyframes carry
the utility prefix so a project declaring a bare `shake` or `pop` animation
cannot redefine them, and both stand down under
`prefers-reduced-motion: reduce` while `$enable-reduced-motion` is on.
- `.color-scheme-{light|dark}` sets the CSS `color-scheme` property, which is
what the browser reads to paint form controls, pickers and scrollbars. It
sets no colours of its own, so it is not a theme switch.
Three existing families were named after an abbreviation rather than the
property they set, which stopped scanning as one set once the floors landed:
- `.mw-100` -> `.max-w-100`, `.mh-100` -> `.max-h-100`. Every floor class on
that axis already spelled the property out (`.min-vw-100`, `.min-vh-100`,
`.min-dvw-100`), so `.mw` sitting next to the new `.min-w` read as two
conventions for one property.
- `.link-offset-*` -> `.underline-offset-*`, keeping the three steps and the
`-hover` variants. The class sets `text-underline-offset` on any element, not
only a link, and `.underline-thickness-*` beside it already carried the
property-based name.
Every removed class is declared in class-api-removals.json with the class that
replaces it, so the class API gate keeps answering for it.
Documents all of it on the pages that own it: `## Min width` / `## Min height`,
`.border-x` / `.border-y` on the border page, `## Animation` on the motion page,
and a new color scheme page in the sidebar. Adds the `## Customizing` heading
the motion page was missing above its `### Utilities API` section.
Raises the two utilities bundlewatch ceilings: the new families put
coreui-utilities.min.css over its limit, and the unminified file had 40 bytes of
headroom left, so it would have tripped on whichever change landed next.
Coverage Report for CI Build 33567782190Coverage decreased (-0.007%) to 93.079%Details
Uncovered ChangesNo uncovered changes found. Coverage Regressions1 previously-covered line in 1 file lost coverage.
Coverage Stats💛 - Coveralls |
…let the derived colors take their opacity Three more gaps against the utility set, all in families we already ship. **`.link-underline-*` is `.underline-*`.** `.link-underline-danger` becomes `.underline-danger`, and the opacity steps become `.underline-opacity-*`. The class sets `text-decoration-color` on any element, not only a link, and it now sits beside `.underline-offset-*` and `.underline-thickness-*`, which already carried that name. Deliberate divergence: the opacity keeps its `-opacity-` segment rather than folding into the colour class, because every other opacity family here reads `.bg-opacity-*` / `.text-opacity-*` / `.link-opacity-*`, and one family spelling it differently is what this pass removes. The unrelated `--cui-link-underline-offset` token from Reboot keeps its name. **New `.rounded-size-*`.** It writes the radius into `--cui-rounded-size` without applying `border-radius`, and the unprefixed corner classes now read that token, so one size class drives whichever corners you round. Without the indirection the utility would compile and do nothing, so `.rounded`, `.rounded-top`, `.rounded-end`, `.rounded-bottom` and `.rounded-start` fall back to `--cui-border-radius` when no size is in scope — existing markup renders unchanged, and the numbered classes keep setting their own value. **The derived colour families compose with their opacity classes.** `.bg-*-subtle`, `.bg-*-muted`, `.border-*-subtle` and `.text-*-emphasis` each wrote the property directly, so `.bg-opacity-50` did nothing on `.bg-primary-subtle` while working on `.bg-primary`. They now declare `--cui-bg` / `--cui-border` / `--cui-text` and resolve through the same `color-mix()` as the base families; at full opacity the output is identical. Folds the `link-underline-dark` / `link-underline-light` removal entries into the `link-underline-*` pattern that now covers them, keeping the note that those two have no renamed form because the theme colours are gone. Raises the ceilings again: routing four families through `color-mix()` costs bytes that a size pass at the end of v6 is the place to win back.
…prefixes first The docs job runs `npm ci && npm run docs-build` and never rebuilds the stylesheet, while <UtilityClasses /> reads `dist/css/coreui.css` — the committed one, refreshed only by the occasional `chore: rebuild dist`. So every PR that adds a utility class fails the docs build until that rebuild lands, and the error names the class rather than the staleness. `docs-build` now runs `css-compile` first, so the table is generated from the branch it documents. <UtilityClasses /> walks its `utility` list in order and skips classes an earlier family already claimed, and its prefix match is greedy: `underline` also matches `.underline-offset-*`, `.underline-opacity-*` and `.underline-thickness-*`, and `rounded` matches `.rounded-size-*`. Listing the narrow prefixes first leaves the broad one only what it owns, which fills three tables that had been rendering empty — one of them before this branch. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
mrholek
added a commit
that referenced
this pull request
Sep 1, 2026
…rline families after their properties (#1044) * feat(utilities)!: close the utility gaps and name the sizing and underline families after their properties Four families had no class in our build at all: - `.border-x` / `.border-y` (and their `-0` forms) set the `border-inline` and `border-block` shorthands, so a facing pair of borders takes one class instead of two. - `.min-w-{0|100}` and `.min-h-{0|100}` set a floor where only caps existed. `.min-w-0` is the one that earns its place: a flex child starts at `min-width: auto` and a `.text-truncate` child never breaks its line, so the item pushes past its track instead of ellipsizing until the floor drops to 0. - `.animation-{shake|pop}` ship their keyframes with them. The keyframes carry the utility prefix so a project declaring a bare `shake` or `pop` animation cannot redefine them, and both stand down under `prefers-reduced-motion: reduce` while `$enable-reduced-motion` is on. - `.color-scheme-{light|dark}` sets the CSS `color-scheme` property, which is what the browser reads to paint form controls, pickers and scrollbars. It sets no colours of its own, so it is not a theme switch. Three existing families were named after an abbreviation rather than the property they set, which stopped scanning as one set once the floors landed: - `.mw-100` -> `.max-w-100`, `.mh-100` -> `.max-h-100`. Every floor class on that axis already spelled the property out (`.min-vw-100`, `.min-vh-100`, `.min-dvw-100`), so `.mw` sitting next to the new `.min-w` read as two conventions for one property. - `.link-offset-*` -> `.underline-offset-*`, keeping the three steps and the `-hover` variants. The class sets `text-underline-offset` on any element, not only a link, and `.underline-thickness-*` beside it already carried the property-based name. Every removed class is declared in class-api-removals.json with the class that replaces it, so the class API gate keeps answering for it. Documents all of it on the pages that own it: `## Min width` / `## Min height`, `.border-x` / `.border-y` on the border page, `## Animation` on the motion page, and a new color scheme page in the sidebar. Adds the `## Customizing` heading the motion page was missing above its `### Utilities API` section. Raises the two utilities bundlewatch ceilings: the new families put coreui-utilities.min.css over its limit, and the unminified file had 40 bytes of headroom left, so it would have tripped on whichever change landed next. * feat(utilities)!: rename the underline family, add rounded-size, and let the derived colors take their opacity Three more gaps against the utility set, all in families we already ship. **`.link-underline-*` is `.underline-*`.** `.link-underline-danger` becomes `.underline-danger`, and the opacity steps become `.underline-opacity-*`. The class sets `text-decoration-color` on any element, not only a link, and it now sits beside `.underline-offset-*` and `.underline-thickness-*`, which already carried that name. Deliberate divergence: the opacity keeps its `-opacity-` segment rather than folding into the colour class, because every other opacity family here reads `.bg-opacity-*` / `.text-opacity-*` / `.link-opacity-*`, and one family spelling it differently is what this pass removes. The unrelated `--cui-link-underline-offset` token from Reboot keeps its name. **New `.rounded-size-*`.** It writes the radius into `--cui-rounded-size` without applying `border-radius`, and the unprefixed corner classes now read that token, so one size class drives whichever corners you round. Without the indirection the utility would compile and do nothing, so `.rounded`, `.rounded-top`, `.rounded-end`, `.rounded-bottom` and `.rounded-start` fall back to `--cui-border-radius` when no size is in scope — existing markup renders unchanged, and the numbered classes keep setting their own value. **The derived colour families compose with their opacity classes.** `.bg-*-subtle`, `.bg-*-muted`, `.border-*-subtle` and `.text-*-emphasis` each wrote the property directly, so `.bg-opacity-50` did nothing on `.bg-primary-subtle` while working on `.bg-primary`. They now declare `--cui-bg` / `--cui-border` / `--cui-text` and resolve through the same `color-mix()` as the base families; at full opacity the output is identical. Folds the `link-underline-dark` / `link-underline-light` removal entries into the `link-underline-*` pattern that now covers them, keeping the note that those two have no renamed form because the theme colours are gone. Raises the ceilings again: routing four families through `color-mix()` costs bytes that a size pass at the end of v6 is the place to win back. * fix(docs): compile the CSS the utility tables read, and order narrow prefixes first The docs job runs `npm ci && npm run docs-build` and never rebuilds the stylesheet, while <UtilityClasses /> reads `dist/css/coreui.css` — the committed one, refreshed only by the occasional `chore: rebuild dist`. So every PR that adds a utility class fails the docs build until that rebuild lands, and the error names the class rather than the staleness. `docs-build` now runs `css-compile` first, so the table is generated from the branch it documents. <UtilityClasses /> walks its `utility` list in order and skips classes an earlier family already claimed, and its prefix match is greedy: `underline` also matches `.underline-offset-*`, `.underline-opacity-*` and `.underline-thickness-*`, and `rounded` matches `.rounded-size-*`. Listing the narrow prefixes first leaves the broad one only what it owns, which fills three tables that had been rendering empty — one of them before this branch.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Comparing our utility map against
twbs/bootstrap@v6-devclass by class (headings lie here — the same family is documented under different names in both) left 14 families with no class in our build. Nine turned out to be a different name for something we already ship. These are the rest.New families
.border-x,.border-y(+-0)border-inline,border-block.min-w-{0,100},.min-h-{0,100}min-width,min-height.animation-{shake,pop},.animation-noneanimation.color-scheme-{light,dark}color-scheme.min-w-0is the one that earns its place. A flex child starts atmin-width: autoand a.text-truncatechild never breaks its line, so its content width is the whole string — the item pushes past its track instead of ellipsizing. The example on the width page shows both rows.The animation keyframes are named
animation-shake/animation-pop, notshake/pop, so a project declaring an animation under either bare name cannot redefine ours. Both stand down underprefers-reduced-motion: reducewhile$enable-reduced-motionis on..color-scheme-*sets no colours of its own — it tells the browser which scheme to paint form controls, pickers and scrollbars in. The page says so explicitly, because the name reads like a theme switch and is not one.Renames
Three families were named after an abbreviation rather than the property they set, which stopped scanning as one set once the floors landed.
.mw-100.max-w-100.mh-100.max-h-100.link-offset-*(+-hover).underline-offset-*(+-hover)Every floor class on the sizing axis already spelled the property out (
.min-vw-100,.min-vh-100,.min-dvw-100), so.mwsitting next to the new.min-wread as two conventions for one property..link-offset-*setstext-underline-offseton any element, not only a link, and.underline-thickness-*beside it already carried the property-based name. Same three steps, same values, same-hovervariants.Each removed class is declared in
build/class-api-removals.jsonwith the class that replaces it, so the class API gate keeps answering for it.Docs
New
## Min width/## Min heightsections,.border-x/.border-yon the border page,## Animationon the motion page, a new color scheme page in the sidebar, and migration-guide entries for all of it. The motion page also gains the## Customizingheading it was missing above### Utilities API.Verification
npm run css-compile— all ten new classes present indist/css/coreui.css; both keyframes emitted outside the cascade layer, and theprefers-reduced-motion: reduceoverride in place.node build/check-class-api.mjs— passes; the three removals match 8 baseline classes.npx stylelint scss/_utilities.scss scss/utilities/_api.scss— clean.npm run docs-build— 178 pages, no broken links.coreui-utilities.min.css20 bytes over, and the unminified file had 40 bytes of headroom left, so it would have tripped on whichever change landed next. Now 19.43/19.75 kB and 17.75/18 kB.Second pass
.link-underline-*→.underline-*.link-underline,.link-underline-danger.underline,.underline-danger.link-underline-opacity-50(+-hover).underline-opacity-50(+-hover)Same reasoning as
.link-offset-*: the class setstext-decoration-coloron any element, not only a link, and.underline-offset-*/.underline-thickness-*beside it already carried the property-based name.Deliberate divergence: upstream folds the opacity into the colour class (
.underline-50); we keep.underline-opacity-50, because every other opacity family here reads.bg-opacity-*/.text-opacity-*/.link-opacity-*. One family spelling it differently is exactly what this pass removes.The unrelated
--cui-link-underline-offsettoken from Reboot keeps its name — it is not this utility.New
.rounded-size-*Writes the radius into
--cui-rounded-sizewithout applyingborder-radius. This only means anything if something reads the token, so the unprefixed corner classes now do:One size class then drives whichever corners you round (
.rounded-size-5 > .rounded-top). With no size in scope they fall back to--cui-border-radius, so existing markup renders unchanged, and the numbered classes (.rounded-3,.rounded-top-3) set their own value and ignore the token.The derived colour families now take their opacity classes
.bg-*-subtle,.bg-*-muted,.border-*-subtleand.text-*-emphasiswrote the property directly, so.bg-opacity-50did nothing on.bg-primary-subtlewhile working on.bg-primary:At full opacity the output is identical.
Not in this PR
.fg-contrast-*is not a standalone class upstream — it belongs to a rename of the whole text-colour family (.text-primary→.fg-primary,.text-opacity-50→.fg-50,.text-reset→.fg-reset,.text-*-emphasis→.fg-emphasis-*), together with the word-order flip on.bg-*-subtle→.bg-subtle-*. Measured blast radius across the three repos: 760 uses of.text-{color}(74 of them in React/Vue component source) plus 122text-opacity/text-bg, before counting admin templates and user code. That is its own decision and its own PR.Verification (second pass)
node build/check-class-api.mjs— passes. It caught that the newlink-underline-*pattern swallowed the existinglink-underline-dark/-lightentries; they are folded in with their reason kept.npx stylelint scss/_utilities.scss— clean.npm run docs-build— 178 pages, no broken links.coreui.min.css65.88/66.25 kB,coreui-utilities.min.css18.38/18.5 kB). Routing four families throughcolor-mix()costs bytes the end-of-v6 size pass is the place to win back.