From 4950847954d3f4c73776858a3eeb0ac37771c965 Mon Sep 17 00:00:00 2001
From: mrholek
Date: Wed, 2 Sep 2026 00:10:12 +0200
Subject: [PATCH 1/3] 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.
---
.bundlewatch.config.json | 4 +-
build/class-api-removals.json | 3 +
docs/src/content/docs/content/reboot.mdx | 2 +-
docs/src/content/docs/migration/v6.mdx | 22 ++++++-
docs/src/content/docs/utilities/border.mdx | 14 +++--
.../content/docs/utilities/color-scheme.mdx | 35 +++++++++++
docs/src/content/docs/utilities/height.mdx | 22 ++++++-
docs/src/content/docs/utilities/link.mdx | 26 ++++----
docs/src/content/docs/utilities/motion.mdx | 27 +++++++-
docs/src/content/docs/utilities/width.mdx | 21 ++++++-
docs/src/data/sidebar.yml | 2 +
scss/_utilities.scss | 63 +++++++++++++++++--
scss/utilities/_api.scss | 41 ++++++++++++
13 files changed, 248 insertions(+), 34 deletions(-)
create mode 100644 docs/src/content/docs/utilities/color-scheme.mdx
diff --git a/.bundlewatch.config.json b/.bundlewatch.config.json
index 99aa8f8aa..9e6b2eb3e 100644
--- a/.bundlewatch.config.json
+++ b/.bundlewatch.config.json
@@ -18,11 +18,11 @@
},
{
"path": "./dist/css/coreui-utilities.css",
- "maxSize": "19.5 kB"
+ "maxSize": "19.75 kB"
},
{
"path": "./dist/css/coreui-utilities.min.css",
- "maxSize": "17.75 kB"
+ "maxSize": "18 kB"
},
{
"path": "./dist/css/coreui.css",
diff --git a/build/class-api-removals.json b/build/class-api-removals.json
index 4cfcffaa6..7e870356d 100644
--- a/build/class-api-removals.json
+++ b/build/class-api-removals.json
@@ -147,6 +147,7 @@
"link-dark": "The theme classes colour a link now: Reboot resolves a link's colour from --cui-theme-fg, which every .theme-* class sets, so replaces . The theme slot is a tone picked for text rather than the raw brand colour - it darkens on a light background and lightens on a dark one, where .link-* gave the same colour in both. Pair it with .link-underline for the underline, which now defaults to currentColor. For .link-body-emphasis use .text-body-emphasis.",
"link-info": "The theme classes colour a link now: Reboot resolves a link's colour from --cui-theme-fg, which every .theme-* class sets, so replaces . The theme slot is a tone picked for text rather than the raw brand colour - it darkens on a light background and lightens on a dark one, where .link-* gave the same colour in both. Pair it with .link-underline for the underline, which now defaults to currentColor. For .link-body-emphasis use .text-body-emphasis.",
"link-light": "The theme classes colour a link now: Reboot resolves a link's colour from --cui-theme-fg, which every .theme-* class sets, so replaces . The theme slot is a tone picked for text rather than the raw brand colour - it darkens on a light background and lightens on a dark one, where .link-* gave the same colour in both. Pair it with .link-underline for the underline, which now defaults to currentColor. For .link-body-emphasis use .text-body-emphasis.",
+ "link-offset-*": "Renamed to .underline-offset-* in v6, keeping the same scale and the -hover variants. The class sets text-underline-offset on any element, not only a link, and it now sits next to .underline-thickness-* which already carried that name: .link-offset-2-hover becomes .underline-offset-2-hover.",
"link-primary": "The theme classes colour a link now: Reboot resolves a link's colour from --cui-theme-fg, which every .theme-* class sets, so replaces . The theme slot is a tone picked for text rather than the raw brand colour - it darkens on a light background and lightens on a dark one, where .link-* gave the same colour in both. Pair it with .link-underline for the underline, which now defaults to currentColor. For .link-body-emphasis use .text-body-emphasis.",
"link-secondary": "The theme classes colour a link now: Reboot resolves a link's colour from --cui-theme-fg, which every .theme-* class sets, so replaces . The theme slot is a tone picked for text rather than the raw brand colour - it darkens on a light background and lightens on a dark one, where .link-* gave the same colour in both. Pair it with .link-underline for the underline, which now defaults to currentColor. For .link-body-emphasis use .text-body-emphasis.",
"link-success": "The theme classes colour a link now: Reboot resolves a link's colour from --cui-theme-fg, which every .theme-* class sets, so replaces . The theme slot is a tone picked for text rather than the raw brand colour - it darkens on a light background and lightens on a dark one, where .link-* gave the same colour in both. Pair it with .link-underline for the underline, which now defaults to currentColor. For .link-body-emphasis use .text-body-emphasis.",
@@ -155,11 +156,13 @@
"link-warning": "The theme classes colour a link now: Reboot resolves a link's colour from --cui-theme-fg, which every .theme-* class sets, so replaces . The theme slot is a tone picked for text rather than the raw brand colour - it darkens on a light background and lightens on a dark one, where .link-* gave the same colour in both. Pair it with .link-underline for the underline, which now defaults to currentColor. For .link-body-emphasis use .text-body-emphasis.",
"list-group-item-dark": "Removed in v6 with the `dark` theme colour. Rename it to the `-inverse` variant: `inverse` resolves to gray-900 in light mode and gray-100 in dark, so it stands against the page in both schemes instead of staying near-black on a near-black one.",
"list-group-item-light": "Removed in v6 with the `light` theme colour. Rename it to the `-secondary` variant, which is the light neutral now (gray-100 in light mode, gray-600 in dark). Where the class was painting a plain panel rather than naming a colour, use .bg-4 or .bg-3.",
+ "mh-100": "Renamed to .max-h-100 in v6. The abbreviation clashed with the new .min-h-* family — .mh next to .min-h reads as two conventions on one axis, so both ends spell the property out now: .max-h-100, .min-h-0, .min-h-100.",
"modal-backdrop": "The backdrop is the native ::backdrop pseudo-element now - no element is inserted into the DOM. Style it through the --cui-modal-backdrop-bg / --cui-modal-backdrop-opacity tokens on .modal.",
"modal-content": "Modal renders on the native element now: the positioning shell and the content box are the single element, so the wrapper layers are gone. Size and fullscreen modifiers sit on the dialog itself. Legacy v5 markup is auto-upgraded at runtime (with a console warning) until v7 - see \"Modal and Offcanvas on the native dialog\" in the migration guide.",
"modal-dialog": "Modal renders on the native element now: the positioning shell and the content box are the single element, so the wrapper layers are gone. Size and fullscreen modifiers sit on the dialog itself. Legacy v5 markup is auto-upgraded at runtime (with a console warning) until v7 - see \"Modal and Offcanvas on the native dialog\" in the migration guide.",
"modal-dialog-centered": "The browser centers a native modal in the viewport, so centering needs no class - it is the default and only behavior.",
"modal-dialog-scrollable": "Renamed to .modal-scrollable, placed on the itself now that the wrapper layers are gone.",
+ "mw-100": "Renamed to .max-w-100 in v6. The abbreviation clashed with the new .min-w-* family — .mw next to .min-w reads as two conventions on one axis, so both ends spell the property out now: .max-w-100, .min-w-0, .min-w-100.",
"offcanvas-backdrop": "The backdrop is the native ::backdrop pseudo-element now - no element is inserted into the DOM. Style it through the --cui-offcanvas-backdrop-bg / --cui-offcanvas-backdrop-opacity tokens on .offcanvas.",
"pointer-event": "Touch is handled natively by the scroll viewport now - drag, swipe and momentum come from the browser, so the touch-action override class has nothing to do.",
"progress-group": "Removed in v6 — a CoreUI-only addition that utilities already cover. It was a flex row with a 100px label column, a header row and a 2px gap between bars. Use .hstack / .vstack with a gap-* utility and put the label column's width in a style; see \"Grouping bars\" on the Progress page.",
diff --git a/docs/src/content/docs/content/reboot.mdx b/docs/src/content/docs/content/reboot.mdx
index 27ba044d0..b7c13c268 100644
--- a/docs/src/content/docs/content/reboot.mdx
+++ b/docs/src/content/docs/content/reboot.mdx
@@ -53,7 +53,7 @@ The `` and `` elements are updated to provide better page-wide defau
## Links
-Links take `--cui-link-color` and switch to `--cui-link-hover-color` on hover. Inside anything that sets a theme, both follow it: the link reads `--cui-theme-fg` and hovers to `--cui-theme-fg-emphasis`, so a link on a colored surface stays part of that surface. The underline sits `.2em` below the text through `--cui-link-underline-offset` (Sass knob: `$link-underline-offset`), clearing descenders; the [`.link-offset-*` utilities](/utilities/link/#underline-offset) adjust it per link, and setting the token to `auto` restores the browser default.
+Links take `--cui-link-color` and switch to `--cui-link-hover-color` on hover. Inside anything that sets a theme, both follow it: the link reads `--cui-theme-fg` and hovers to `--cui-theme-fg-emphasis`, so a link on a colored surface stays part of that surface. The underline sits `.2em` below the text through `--cui-link-underline-offset` (Sass knob: `$link-underline-offset`), clearing descenders; the [`.underline-offset-*` utilities](/utilities/link/#underline-offset) adjust it per link, and setting the token to `auto` restores the browser default.
Placeholder links — an `` with neither `href` nor a class — keep the surrounding text color and get no underline, so an anchor waiting for its URL doesn't read as a working link.
diff --git a/docs/src/content/docs/migration/v6.mdx b/docs/src/content/docs/migration/v6.mdx
index 4885ef7dd..d8f2fa20a 100644
--- a/docs/src/content/docs/migration/v6.mdx
+++ b/docs/src/content/docs/migration/v6.mdx
@@ -1599,7 +1599,7 @@ Multi Select's option indicators moved with it — they render as a decorative
- **The underline sits `.2em` below the text.** ` ` reads the new
`--cui-link-underline-offset` token (knob: `$link-underline-offset`), so
underlines clear descenders instead of cutting through them. The
- `.link-offset-*` utilities and `.icon-link` still override it per element;
+ `.underline-offset-*` utilities and `.icon-link` still override it per element;
set the token to `auto` for the old browser default.
- **`accent-color: var(--cui-primary)` on `:root`.** Native controls we don't
restyle — a bare checkbox, radio, range or `` — follow the brand
@@ -3665,3 +3665,23 @@ Prefer the `.d-sidebar-narrow*` names in new markup.
support the media feature gets no transition at all. Rules that force
`transition: none` stay outside the query — they disable motion on purpose,
so they have to reach every reader.
+- **The sizing utilities spell the property out: `.mw-100` → `.max-w-100`,
+ `.mh-100` → `.max-h-100`.** The abbreviations were the only ones on that axis:
+ the floor classes are `.min-vw-100`, `.min-vh-100`, `.min-dvw-100`, and the
+ new `.min-w-*` / `.min-h-*` join them, so `.mw` sitting next to `.min-w` read
+ as two conventions for one property. Both ends are now written the same way.
+- **`.link-offset-*` is `.underline-offset-*`.** Same three steps, same
+ `-hover` variants — `.link-offset-2-hover` becomes
+ `.underline-offset-2-hover`. The class sets `text-underline-offset` on any
+ element, not just a link, and `.underline-thickness-*` next to it already
+ carried the property-based name.
+- **New utilities:** `.border-x` / `.border-y` (and their `-0` forms) set the
+ `border-inline` / `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 —
+ `.min-w-0` is what releases the `min-width: auto` on a flex child so a
+ `.text-truncate` sibling can actually truncate; `.animation-{shake|pop}` ship
+ their keyframes with them, prefixed so they cannot be redefined by a bare
+ `shake` or `pop` of your own, and stand down under
+ `prefers-reduced-motion: reduce`; `.color-scheme-{light|dark}` sets CSS
+ `color-scheme`, which is what the browser reads to paint form controls and
+ scrollbars — it changes no colours of its own.
diff --git a/docs/src/content/docs/utilities/border.mdx b/docs/src/content/docs/utilities/border.mdx
index c4e0cc51c..3515e3117 100644
--- a/docs/src/content/docs/utilities/border.mdx
+++ b/docs/src/content/docs/utilities/border.mdx
@@ -2,18 +2,22 @@
title: "Bootstrap 6 Border"
name: "Border"
description: "Use border utilities to add or remove an element's borders, and to set how thick they are."
-utility: [{prefix: "border", property: "border"}, {prefix: "border", property: "border-width"}, {prefix: "border", property: "border-top-width"}, {prefix: "border", property: "border-inline-end-width"}, {prefix: "border", property: "border-bottom-width"}, {prefix: "border", property: "border-inline-start-width"}]
+utility: [{prefix: "border", property: "border"}, {prefix: "border", property: "border-inline"}, {prefix: "border", property: "border-block"}, {prefix: "border", property: "border-width"}, {prefix: "border", property: "border-top-width"}, {prefix: "border", property: "border-inline-end-width"}, {prefix: "border", property: "border-bottom-width"}, {prefix: "border", property: "border-inline-start-width"}]
---
## Additive
-Use border utilities to add or remove an element's borders. Choose from all borders or one at a time.
+Use border utilities to add or remove an element's borders. Choose from all borders, one at a time, or a facing pair — `.border-x` draws the two inline sides, `.border-y` the two block sides.
- `} />
+
+
+ `} />
+
+ `.border-x` and `.border-y` set the `border-inline` and `border-block` shorthands, so they follow the writing direction the way the single-side classes do.
## Subtractive
@@ -23,7 +27,9 @@ Or remove borders:
- `} />
+
+
+ `} />
## Width
diff --git a/docs/src/content/docs/utilities/color-scheme.mdx b/docs/src/content/docs/utilities/color-scheme.mdx
new file mode 100644
index 000000000..d6f4cea47
--- /dev/null
+++ b/docs/src/content/docs/utilities/color-scheme.mdx
@@ -0,0 +1,35 @@
+---
+title: "Bootstrap 6 Color Scheme"
+name: "Color scheme"
+description: "Tell the browser which color scheme an element renders in, so form controls, scrollbars and other built-in UI follow it."
+utility: "color-scheme"
+---
+
+## How it works
+
+ `.color-scheme-light` and `.color-scheme-dark` set the CSS `color-scheme` property. That property is what the browser reads to paint the parts of the page it draws itself — checkbox and radio ticks, the date and color picker popups, `` menus, scrollbars, and the canvas behind a transparent background.
+
+Our theme classes already set it: `:root` renders in `light`, and the dark theme switches it to `dark`. Reach for these utilities on the exception — a panel that keeps one scheme while the page is in the other.
+
+
+ Light scheme
+
+
+
+ Dark scheme
+
+
`} />
+
+## Not a theme
+
+The property changes only the browser's own rendering. It sets no colors of its own, so text and backgrounds stay whatever your CSS says — `.color-scheme-dark` on a white panel leaves the panel white and gives it dark scrollbars.
+
+To flip the colors as well, set the theme with `data-coreui-theme="dark"`, which switches the tokens **and** the color scheme together. See [color modes](/customize/color-modes/).
+
+## Customizing
+
+### Utilities API
+
+Color scheme utilities are declared in our utilities API in `scss/_utilities.scss`. [Learn how to use the utilities API.](/utilities/api/#using-the-api)
+
+
diff --git a/docs/src/content/docs/utilities/height.mdx b/docs/src/content/docs/utilities/height.mdx
index a957a75a1..d0d6b3f3c 100644
--- a/docs/src/content/docs/utilities/height.mdx
+++ b/docs/src/content/docs/utilities/height.mdx
@@ -2,7 +2,7 @@
title: "Bootstrap 6 Height"
name: "Height"
description: "Set an element's height relative to its parent or to the viewport with our height utilities."
-utility: ["h", "mh", "vh", "min-vh", "dvh", "min-dvh"]
+utility: ["h", "max-h", "min-h", "vh", "min-vh", "dvh", "min-dvh"]
---
## Relative to the parent
@@ -21,12 +21,28 @@ A percentage height resolves against the parent, so the parent needs a height of
## Max height
-`.mh-100` caps an element at the height of its parent, clipping whatever would otherwise overflow it.
+`.max-h-100` caps an element at the height of its parent, clipping whatever would otherwise overflow it.
- Max-height 100%
+ Max-height 100%
`} />
+## Min height
+
+`.min-h-100` sets a floor at the parent's height instead of a cap: the element fills the parent when its content is short, and still grows past it when the content is tall. That is what separates it from `.h-100`, which pins the height and lets the overflow escape.
+
+
+ Min-height 100%
+ `} />
+
+`.min-h-0` releases the same `min-height: auto` floor that `.min-w-0` releases on the other axis. A scrolling child of a flex column needs it — without it the child grows to its content instead of scrolling inside the column.
+
+```html
+
+
Scrolls inside the column
+
+```
+
## Relative to the viewport
`.vh-100` sets the height to the full viewport, `.min-vh-100` sets it as a floor — the usual way to make a page fill the screen even when its content is short.
diff --git a/docs/src/content/docs/utilities/link.mdx b/docs/src/content/docs/utilities/link.mdx
index c192a3d86..3b071c76c 100644
--- a/docs/src/content/docs/utilities/link.mdx
+++ b/docs/src/content/docs/utilities/link.mdx
@@ -2,7 +2,7 @@
title: "Bootstrap 6 Link"
name: "Link"
description: "Link utilities are used to stylize your anchors to adjust their color, opacity, underline offset, underline color, and more."
-utility: ["link-opacity", "link-offset", "link-underline", "underline-thickness"]
+utility: ["link-opacity", "underline-offset", "link-underline", "underline-thickness"]
---
import { getData } from '@coreui/astro-docs/data'
@@ -38,9 +38,9 @@ Change the underline's color independent of the link text color.
Change the underline's distance from your text. Offset is set in `em` units to automatically scale with the element's current `font-size`. Every link already sits on a `.2em` offset from the global `--cui-link-underline-offset` token; these utilities override it per link.
Default link
- Offset 1 link
- Offset 2 link
- Offset 3 link
`} />
+ Offset 1 link
+ Offset 2 link
+ Offset 3 link
`} />
### Underline thickness
@@ -55,18 +55,18 @@ Change how heavy the underline is drawn, on links or any underlined text. Hover
Change the underline's opacity. Requires adding `.link-underline` to first set an `rgba()` color we use to then modify the alpha opacity.
-Underline opacity 0
- Underline opacity 10
- Underline opacity 25
- Underline opacity 50
- Underline opacity 75
- Underline opacity 100
`} />
+Underline opacity 0
+ Underline opacity 10
+ Underline opacity 25
+ Underline opacity 50
+ Underline opacity 75
+ Underline opacity 100
`} />
### Hover variants
-Just like the `.link-opacity-*-hover` utilities, `.link-offset` and `.link-underline-opacity` utilities include `:hover` variants by default. Mix and match to create unique link styles.
+Just like the `.link-opacity-*-hover` utilities, `.underline-offset` and `.link-underline-opacity` utilities include `:hover` variants by default. Mix and match to create unique link styles.
-
+
Underline opacity 0
`} />
@@ -74,7 +74,7 @@ Just like the `.link-opacity-*-hover` utilities, `.link-offset` and `.link-under
Give a link a theme colour with a [`.theme-*`](/customize/theme/) class. Reboot resolves a link's colour from `--cui-theme-fg`, which every theme class sets, so the link follows the theme rather than needing a class of its own.
- `${c.title} link
`), ``, `Emphasis link
`]} />
+ `${c.title} link
`), ``, `Emphasis link
`]} />
`.link-underline` here carries the underline colour, which defaults to `currentColor` — so it follows the theme too, and the opacity utilities have something to fade. Name a colour with `.link-underline-{color}` to break that pairing deliberately.
diff --git a/docs/src/content/docs/utilities/motion.mdx b/docs/src/content/docs/utilities/motion.mdx
index 24198d08c..e1c9a6249 100644
--- a/docs/src/content/docs/utilities/motion.mdx
+++ b/docs/src/content/docs/utilities/motion.mdx
@@ -2,7 +2,7 @@
title: "Bootstrap 6 Motion"
name: "Motion"
description: "Remove the transition from a single element with motion utilities."
-utility: "transition"
+utility: ["transition", "animation"]
---
Components animate with CSS transitions, and the timing is customizable — see [transitions](/customize/transitions/). Add `.transition-none` to set `transition: none` on one element, so its property changes apply instantly instead of animating.
@@ -18,16 +18,39 @@ Hover both buttons. The first eases into its hover colour; the second snaps.
The utility sets the `transition` shorthand, so it removes every transition on the element at once — including any you declared yourself. To retime one component instead of removing its motion, set its `--cui-{component}-transition-duration` to `0s`.
+## Animation
+
+ Two keyframe animations ship as utilities: `.animation-shake` draws attention to something that went wrong, `.animation-pop` scales an element in as it appears. `.animation-none` removes an animation the way `.transition-none` removes a transition.
+
+Shake
+Pop `} />
+
+The keyframes are named `animation-shake` and `animation-pop` rather than `shake` and `pop`, so declaring an animation of your own under either bare name does not redefine ours.
+
+An animation restarts when the class is applied, not when the page loads — so to replay one, remove the class, force a reflow, and add it back:
+
+```js
+element.classList.remove('animation-shake')
+void element.offsetWidth
+element.classList.add('animation-shake')
+```
+
+Both stand down under `prefers-reduced-motion: reduce` while `$enable-reduced-motion` is on, which is the default — the element appears in its final state with no motion.
+
## Reduced motion
-`.transition-none` applies whatever the reader's motion preference is. Reach for it when the motion has to go in every case.
+`.transition-none` and `.animation-none` apply whatever the reader's motion preference is. Reach for them when the motion has to go in every case.
You do not need it to honour that preference: with `$enable-reduced-motion` on, which is the default, every component transition is already wrapped in `prefers-reduced-motion: no-preference`. See [reduced motion](/getting-started/accessibility/#reduced-motion).
To remove every transition at build time instead, set the `$enable-transitions` Sass option to `false`.
+## Customizing
+
### Utilities API
Motion utilities are declared in our utilities API in `scss/_utilities.scss`. [Learn how to use the utilities API.](/utilities/api/#using-the-api)
+
+
diff --git a/docs/src/content/docs/utilities/width.mdx b/docs/src/content/docs/utilities/width.mdx
index 4dcb3893e..573e7b1bc 100644
--- a/docs/src/content/docs/utilities/width.mdx
+++ b/docs/src/content/docs/utilities/width.mdx
@@ -2,7 +2,7 @@
title: "Bootstrap 6 Width"
name: "Width"
description: "Set an element's width relative to its parent or to the viewport with our width utilities."
-utility: ["w", "mw", "vw", "min-vw", "dvw", "min-dvw"]
+utility: ["w", "max-w", "min-w", "vw", "min-vw", "dvw", "min-dvw"]
---
## Relative to the parent
@@ -17,9 +17,24 @@ Width utilities are generated from the utility API in `_utilities.scss`. Include
## Max width
-Use `.mw-100` to cap an element at the width of its parent — the usual reason to reach for it is an image or an SVG that would otherwise overflow.
+Use `.max-w-100` to cap an element at the width of its parent — the usual reason to reach for it is an image or an SVG that would otherwise overflow.
-Max-width 100% Max-width 100% `} />
+Max-width 100% Max-width 100% `} />
+
+## Min width
+
+`.min-w-0` and `.min-w-100` set a floor rather than a cap.
+
+`.min-w-0` is the one you reach for most. A flex or grid child starts at `min-width: auto`, which refuses to shrink below its own content — and a `.text-truncate` child never breaks its line, so its content width is the whole string. The item pushes past its track instead of ellipsizing. Dropping the floor to `0` lets it shrink so the truncation can happen.
+
+
+ Without a floor this line pushes the button out of the box
+ Action
+
+
+
With .min-w-0 the same line truncates instead
+
Action
+
`} />
## Relative to the viewport
diff --git a/docs/src/data/sidebar.yml b/docs/src/data/sidebar.yml
index 79e100ff8..3630bd85b 100644
--- a/docs/src/data/sidebar.yml
+++ b/docs/src/data/sidebar.yml
@@ -381,6 +381,8 @@
to: /utilities/border-color/
- title: Border radius
to: /utilities/border-radius/
+ - title: Color scheme
+ to: /utilities/color-scheme/
- title: Colors
to: /utilities/colors/
- title: Container queries
diff --git a/scss/_utilities.scss b/scss/_utilities.scss
index 91c60770d..696ec49ba 100644
--- a/scss/_utilities.scss
+++ b/scss/_utilities.scss
@@ -314,6 +314,22 @@ $utilities: map.merge(
0: 0,
),
),
+ "border-x": (
+ property: border-inline,
+ class: border-x,
+ values: (
+ null: var(--#{$prefix}border-width) var(--#{$prefix}border-style) var(--#{$prefix}border-color),
+ 0: 0,
+ ),
+ ),
+ "border-y": (
+ property: border-block,
+ class: border-y,
+ values: (
+ null: var(--#{$prefix}border-width) var(--#{$prefix}border-style) var(--#{$prefix}border-color),
+ 0: 0,
+ ),
+ ),
// scss-docs-end utils-border-sides
// scss-docs-start utils-border-color
"border-color": (
@@ -411,9 +427,17 @@ $utilities: map.merge(
),
"max-width": (
property: max-width,
- class: mw,
+ class: max-w,
values: (100: 100%)
),
+ "min-width": (
+ property: min-width,
+ class: min-w,
+ values: (
+ 0: 0,
+ 100: 100%,
+ )
+ ),
"viewport-width": (
property: width,
class: vw,
@@ -450,9 +474,17 @@ $utilities: map.merge(
fit: fit-content
)
),
+ "min-height": (
+ property: min-height,
+ class: min-h,
+ values: (
+ 0: 0,
+ 100: 100%,
+ )
+ ),
"max-height": (
property: max-height,
- class: mh,
+ class: max-h,
values: (100: 100%)
),
"viewport-height": (
@@ -1008,9 +1040,9 @@ $utilities: map.merge(
100: 1
)
),
- "link-offset": (
+ "underline-offset": (
property: text-underline-offset,
- class: link-offset,
+ class: underline-offset,
state: hover,
values: (
1: .125em,
@@ -1258,8 +1290,29 @@ $utilities: map.merge(
values: (
none: none,
)
- )
+ ),
// scss-docs-end utils-transition
+ // scss-docs-start utils-animation
+ "animation": (
+ property: animation,
+ class: animation,
+ values: (
+ none: none,
+ shake: animation-shake .82s cubic-bezier(.36, .07, .19, .97) both,
+ pop: animation-pop .3s cubic-bezier(.34, 1.56, .64, 1) both,
+ )
+ ),
+ // scss-docs-end utils-animation
+ // scss-docs-start utils-color-scheme
+ "color-scheme": (
+ property: color-scheme,
+ class: color-scheme,
+ values: (
+ light: light,
+ dark: dark,
+ )
+ )
+ // scss-docs-end utils-color-scheme
),
$utilities
);
diff --git a/scss/utilities/_api.scss b/scss/utilities/_api.scss
index b92168a78..76d1765c3 100644
--- a/scss/utilities/_api.scss
+++ b/scss/utilities/_api.scss
@@ -51,6 +51,17 @@
}
}
+ // The keyframe animation utilities are decorative, so they stand down when the
+ // user asks for reduced motion.
+ @if map.has-key($utilities, "animation") and $enable-reduced-motion {
+ @media (prefers-reduced-motion: reduce) {
+ .animation-shake,
+ .animation-pop {
+ animation: none;
+ }
+ }
+ }
+
// Print utilities
@media print {
@each $key, $utility in $utilities {
@@ -62,3 +73,33 @@
}
}
}
+
+// Keyframes powering the animation utilities. Declared outside the cascade layer
+// since `@keyframes` registration is global and unaffected by layers — which is
+// also why they carry the utility prefix, so they cannot collide with a `shake`
+// or `pop` animation of your own. Bundles that narrow `$utilities` before
+// importing this file skip them along with the utility.
+@if map.has-key($utilities, "animation") {
+ @keyframes animation-shake {
+ 10%,
+ 90% { transform: translate3d(-1px, 0, 0); }
+
+ 20%,
+ 80% { transform: translate3d(2px, 0, 0); }
+
+ 30%,
+ 50%,
+ 70% { transform: translate3d(-4px, 0, 0); }
+
+ 40%,
+ 60% { transform: translate3d(4px, 0, 0); }
+ }
+
+ // Scales from 0 to 1; the back-easing curve on the utility supplies the slight
+ // overshoot, so the keyframes stay a simple two-stop scale.
+ @keyframes animation-pop {
+ from { transform: scale(0); }
+
+ to { transform: scale(1); }
+ }
+}
From b2d249e2c9387dd6733bbffc9a4b839ad2ae386a Mon Sep 17 00:00:00 2001
From: mrholek
Date: Wed, 2 Sep 2026 00:30:35 +0200
Subject: [PATCH 2/3] feat(utilities)!: rename the underline family, add
rounded-size, and let the derived colors take their opacity
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
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.
---
.bundlewatch.config.json | 6 +-
build/class-api-removals.json | 4 +-
docs/src/content/docs/helpers/icon-link.mdx | 2 +-
docs/src/content/docs/migration/v6.mdx | 37 +++++++++--
.../content/docs/utilities/border-radius.mdx | 14 +++-
docs/src/content/docs/utilities/link.mdx | 26 ++++----
scss/_utilities.scss | 64 ++++++++++++++-----
7 files changed, 109 insertions(+), 44 deletions(-)
diff --git a/.bundlewatch.config.json b/.bundlewatch.config.json
index 9e6b2eb3e..2694e9d2c 100644
--- a/.bundlewatch.config.json
+++ b/.bundlewatch.config.json
@@ -18,11 +18,11 @@
},
{
"path": "./dist/css/coreui-utilities.css",
- "maxSize": "19.75 kB"
+ "maxSize": "20 kB"
},
{
"path": "./dist/css/coreui-utilities.min.css",
- "maxSize": "18 kB"
+ "maxSize": "18.5 kB"
},
{
"path": "./dist/css/coreui.css",
@@ -30,7 +30,7 @@
},
{
"path": "./dist/css/coreui.min.css",
- "maxSize": "65.5 kB"
+ "maxSize": "66.25 kB"
},
{
"path": "./dist/js/coreui.bundle.js",
diff --git a/build/class-api-removals.json b/build/class-api-removals.json
index 7e870356d..d1f1121ba 100644
--- a/build/class-api-removals.json
+++ b/build/class-api-removals.json
@@ -151,8 +151,8 @@
"link-primary": "The theme classes colour a link now: Reboot resolves a link's colour from --cui-theme-fg, which every .theme-* class sets, so replaces . The theme slot is a tone picked for text rather than the raw brand colour - it darkens on a light background and lightens on a dark one, where .link-* gave the same colour in both. Pair it with .link-underline for the underline, which now defaults to currentColor. For .link-body-emphasis use .text-body-emphasis.",
"link-secondary": "The theme classes colour a link now: Reboot resolves a link's colour from --cui-theme-fg, which every .theme-* class sets, so replaces . The theme slot is a tone picked for text rather than the raw brand colour - it darkens on a light background and lightens on a dark one, where .link-* gave the same colour in both. Pair it with .link-underline for the underline, which now defaults to currentColor. For .link-body-emphasis use .text-body-emphasis.",
"link-success": "The theme classes colour a link now: Reboot resolves a link's colour from --cui-theme-fg, which every .theme-* class sets, so replaces . The theme slot is a tone picked for text rather than the raw brand colour - it darkens on a light background and lightens on a dark one, where .link-* gave the same colour in both. Pair it with .link-underline for the underline, which now defaults to currentColor. For .link-body-emphasis use .text-body-emphasis.",
- "link-underline-dark": "Removed in v6 with the `dark` theme colour. Rename it to the `-inverse` variant: `inverse` resolves to gray-900 in light mode and gray-100 in dark, so it stands against the page in both schemes instead of staying near-black on a near-black one.",
- "link-underline-light": "Removed in v6 with the `light` theme colour. Rename it to the `-secondary` variant, which is the light neutral now (gray-100 in light mode, gray-600 in dark). Where the class was painting a plain panel rather than naming a colour, use .bg-4 or .bg-3.",
+ "link-underline": "Renamed to .underline in v6, with the colour variants following it (.link-underline-danger becomes .underline-danger). The class sets text-decoration-color on any element, not only a link, and it now sits next to .underline-offset-* and .underline-thickness-*.",
+ "link-underline-*": "Renamed to .underline-* in v6: .link-underline-danger becomes .underline-danger, and the opacity steps become .underline-opacity-* (.link-underline-opacity-50-hover becomes .underline-opacity-50-hover). The class sets text-decoration-color on any element, not only a link, and it now sits next to .underline-offset-* and .underline-thickness-*. The `dark` and `light` variants have no renamed form — those theme colours are gone in v6: use .underline-inverse for `dark` (gray-900 in light mode, gray-100 in dark, so it stands against the page in both) and .underline-secondary for `light` (the light neutral now).",
"link-warning": "The theme classes colour a link now: Reboot resolves a link's colour from --cui-theme-fg, which every .theme-* class sets, so replaces . The theme slot is a tone picked for text rather than the raw brand colour - it darkens on a light background and lightens on a dark one, where .link-* gave the same colour in both. Pair it with .link-underline for the underline, which now defaults to currentColor. For .link-body-emphasis use .text-body-emphasis.",
"list-group-item-dark": "Removed in v6 with the `dark` theme colour. Rename it to the `-inverse` variant: `inverse` resolves to gray-900 in light mode and gray-100 in dark, so it stands against the page in both schemes instead of staying near-black on a near-black one.",
"list-group-item-light": "Removed in v6 with the `light` theme colour. Rename it to the `-secondary` variant, which is the light neutral now (gray-100 in light mode, gray-600 in dark). Where the class was painting a plain panel rather than naming a colour, use .bg-4 or .bg-3.",
diff --git a/docs/src/content/docs/helpers/icon-link.mdx b/docs/src/content/docs/helpers/icon-link.mdx
index 02b5507fa..3bbbd3927 100644
--- a/docs/src/content/docs/helpers/icon-link.mdx
+++ b/docs/src/content/docs/helpers/icon-link.mdx
@@ -81,7 +81,7 @@ Customize the icon link Sass variables to modify all icon link styles across you
Modify icon links with any of [our link utilities](/utilities/link/) for modifying underline color and offset.
-
+
Icon link
diff --git a/docs/src/content/docs/migration/v6.mdx b/docs/src/content/docs/migration/v6.mdx
index d8f2fa20a..925720ca0 100644
--- a/docs/src/content/docs/migration/v6.mdx
+++ b/docs/src/content/docs/migration/v6.mdx
@@ -2050,19 +2050,19 @@ Multi Select's option indicators moved with it — they render as a decorative
```html
- Primary link
+ Primary link
- Primary link
+ Primary link
```
Two things change beyond the class name. The theme slot is a **tone picked for
text** rather than the raw brand colour — it darkens on a light background and
lightens on a dark one, where `.link-*` gave the same colour in both, so the
link is more readable in dark mode and slightly different in light. And the
- underline needs `.link-underline` to carry it; its colour now defaults to
+ underline needs `.underline` to carry it; its colour now defaults to
`currentColor`, so it follows the theme unless you name one with
- `.link-underline-{color}`.
+ `.underline-{color}`.
`.link-body-emphasis` becomes `.text-body-emphasis`.
@@ -2875,7 +2875,7 @@ now, and the two neutrals swap ends of the gray scale between schemes.
- **Every `-light` and `-dark` class is gone.** Rename `-dark` to `-inverse` and
`-light` to `-secondary` across `alert`, `bg`, `border` (and its per-side
variants), `btn`, `btn-ghost`, `btn-outline`, `btn-subtle`, `callout`, `chip`,
- `focus-ring`, `link-underline`, `list-group-item`, `shadow`, `table`, `text`,
+ `focus-ring`, `underline`, `list-group-item`, `shadow`, `table`, `text`,
`text-bg` and `theme`, plus the `-subtle` and `-emphasis` families. The
colour-scheme opt-ins keep their names: `.navbar-dark`, `.dropdown-menu-dark`,
`.carousel-dark` and `.sidebar-dark` are unaffected, as is `.fw-light`.
@@ -3075,7 +3075,7 @@ and still compose. What changes is where the translucency expression lives:
ignored the opacity knob every other colour utility honoured.
- Every custom property a utility assigns is registered
`@property { inherits: false }`, which now covers `--cui-bg`, `--cui-text`,
- `--cui-border` and `--cui-link-underline` as well — so a colour set on a
+ `--cui-border` and `--cui-underline` as well — so a colour set on a
container cannot leak into a descendant that only carries an opacity class.
`at-property: false` still opts a utility out of that registration, for the one
@@ -3215,7 +3215,7 @@ Five names are now registered with
`inherits: false`, so each utility is scoped to the element it is applied to:
`--cui-text-opacity`, `--cui-bg-opacity`,
`--cui-border-opacity`, `--cui-link-opacity`,
-`--cui-link-underline-opacity`. Upstream v6 does the same.
+`--cui-underline-opacity`. Upstream v6 does the same.
- **If you relied on the leak** — one `.link-opacity-*` on a wrapper fading a
whole block of links — put the utility on each element, or set the custom
@@ -3685,3 +3685,26 @@ Prefer the `.d-sidebar-narrow*` names in new markup.
`prefers-reduced-motion: reduce`; `.color-scheme-{light|dark}` sets CSS
`color-scheme`, which is what the browser reads to paint form controls and
scrollbars — it changes no colours of its own.
+- **`.link-underline-*` is `.underline-*`.** `.link-underline-danger` becomes
+ `.underline-danger`, and the opacity steps become `.underline-opacity-*`
+ (`.link-underline-opacity-50-hover` → `.underline-opacity-50-hover`). Like
+ `.underline-offset-*`, the class sets a `text-decoration-*` property on any
+ element rather than only on a link, and it now sits beside
+ `.underline-thickness-*`, which already carried that name. **We keep the
+ `-opacity-` segment** where upstream folds opacity into the colour class as
+ `.underline-50` — every other opacity family here reads
+ `.bg-opacity-*` / `.text-opacity-*` / `.link-opacity-*`, and one family
+ spelling it differently is what this pass is removing.
+- **New `.rounded-size-*`.** It writes the radius into `--cui-rounded-size`
+ without setting `border-radius` itself, and the unprefixed corner classes
+ (`.rounded`, `.rounded-top`, `.rounded-end`, `.rounded-bottom`,
+ `.rounded-start`) now read that token, so one size class drives whichever
+ corners you round. With no `.rounded-size-*` in scope they fall back to
+ `--cui-border-radius`, so existing markup renders unchanged; the numbered
+ classes (`.rounded-3`, `.rounded-top-3`) set their own value and ignore it.
+- **The derived colour utilities compose with their opacity classes.**
+ `.bg-*-subtle`, `.bg-*-muted`, `.border-*-subtle` and `.text-*-emphasis` each
+ wrote the property directly, so `.bg-opacity-50` on a `.bg-primary-subtle`
+ did nothing while the same class on `.bg-primary` worked. They now declare
+ `--cui-bg` / `--cui-border` / `--cui-text` and resolve through the same
+ `color-mix()` as the base families. Nothing changes at full opacity.
diff --git a/docs/src/content/docs/utilities/border-radius.mdx b/docs/src/content/docs/utilities/border-radius.mdx
index 1bcdda035..240e0ecec 100644
--- a/docs/src/content/docs/utilities/border-radius.mdx
+++ b/docs/src/content/docs/utilities/border-radius.mdx
@@ -2,7 +2,7 @@
title: "Bootstrap 6 Border Radius"
name: "Border radius"
description: "Round the corners of an element with our border-radius utilities, from a subtle radius to a full pill."
-utility: "rounded"
+utility: ["rounded", "rounded-size"]
---
## Examples
@@ -34,6 +34,18 @@ Use the scaling classes for larger or smaller rounded corners. Sizes range from
Completely round image
Rounded pill image `} />
+## Setting the size without applying it
+
+ `.rounded-size-*` writes the radius into `--cui-rounded-size` without setting `border-radius` itself. The unprefixed corner classes — `.rounded`, `.rounded-top`, `.rounded-end`, `.rounded-bottom`, `.rounded-start` — read that token, so one size class drives whichever corners you round.
+
+
+ Top
+ Start
+ All
+ `} />
+
+Without a `.rounded-size-*` in scope the corner classes fall back to `--cui-border-radius`, so existing markup is unaffected. Numbered classes such as `.rounded-3` and `.rounded-top-3` set their own value and ignore the token.
+
## Customizing
### CSS variables
diff --git a/docs/src/content/docs/utilities/link.mdx b/docs/src/content/docs/utilities/link.mdx
index 3b071c76c..3ecda81ae 100644
--- a/docs/src/content/docs/utilities/link.mdx
+++ b/docs/src/content/docs/utilities/link.mdx
@@ -2,7 +2,7 @@
title: "Bootstrap 6 Link"
name: "Link"
description: "Link utilities are used to stylize your anchors to adjust their color, opacity, underline offset, underline color, and more."
-utility: ["link-opacity", "underline-offset", "link-underline", "underline-thickness"]
+utility: ["link-opacity", "underline", "underline-offset", "underline-opacity", "underline-thickness"]
---
import { getData } from '@coreui/astro-docs/data'
@@ -31,7 +31,7 @@ You can even change the opacity level on hover.
Change the underline's color independent of the link text color.
- `${c.title} underline
`)} />
+ `${c.title} underline
`)} />
### Underline offset
@@ -53,20 +53,20 @@ Change how heavy the underline is drawn, on links or any underlined text. Hover
### Underline opacity
-Change the underline's opacity. Requires adding `.link-underline` to first set an `rgba()` color we use to then modify the alpha opacity.
+Change the underline's opacity. Requires adding `.underline` to first set an `rgba()` color we use to then modify the alpha opacity.
-Underline opacity 0
- Underline opacity 10
- Underline opacity 25
- Underline opacity 50
- Underline opacity 75
- Underline opacity 100
`} />
+Underline opacity 0
+ Underline opacity 10
+ Underline opacity 25
+ Underline opacity 50
+ Underline opacity 75
+ Underline opacity 100
`} />
### Hover variants
-Just like the `.link-opacity-*-hover` utilities, `.underline-offset` and `.link-underline-opacity` utilities include `:hover` variants by default. Mix and match to create unique link styles.
+Just like the `.link-opacity-*-hover` utilities, `.underline-offset` and `.underline-opacity` utilities include `:hover` variants by default. Mix and match to create unique link styles.
-
+
Underline opacity 0
`} />
@@ -74,9 +74,9 @@ Just like the `.link-opacity-*-hover` utilities, `.underline-offset` and `.link-
Give a link a theme colour with a [`.theme-*`](/customize/theme/) class. Reboot resolves a link's colour from `--cui-theme-fg`, which every theme class sets, so the link follows the theme rather than needing a class of its own.
- `${c.title} link
`), ``, `Emphasis link
`]} />
+ `${c.title} link
`), ``, `Emphasis link
`]} />
-`.link-underline` here carries the underline colour, which defaults to `currentColor` — so it follows the theme too, and the opacity utilities have something to fade. Name a colour with `.link-underline-{color}` to break that pairing deliberately.
+`.underline` here carries the underline colour, which defaults to `currentColor` — so it follows the theme too, and the opacity utilities have something to fade. Name a colour with `.underline-{color}` to break that pairing deliberately.
The theme slot is a tone picked for text, not the raw brand colour: it darkens on a light background and lightens on a dark one, which is why the same class reads on both.
diff --git a/scss/_utilities.scss b/scss/_utilities.scss
index 696ec49ba..4d8f128d9 100644
--- a/scss/_utilities.scss
+++ b/scss/_utilities.scss
@@ -391,7 +391,11 @@ $utilities: map.merge(
// scss-docs-end utils-border-width
// scss-docs-start utils-border-color-subtle
"subtle-border-color": (
- property: border-color,
+ property: (
+ --#{$prefix}border: null,
+ --#{$prefix}border-opacity: 1,
+ border-color: color-mix(in srgb, var(--#{$prefix}border) calc(var(--#{$prefix}border-opacity) * 100%), transparent),
+ ),
class: border,
values: $utilities-border-subtle
),
@@ -1022,7 +1026,11 @@ $utilities: map.merge(
)
),
"text-color": (
- property: color,
+ property: (
+ --#{$prefix}text: null,
+ --#{$prefix}text-opacity: 1,
+ color: color-mix(in srgb, var(--#{$prefix}text) calc(var(--#{$prefix}text-opacity) * 100%), transparent),
+ ),
class: text,
values: $utilities-text-emphasis-colors
),
@@ -1062,13 +1070,13 @@ $utilities: map.merge(
5: 5px,
)
),
- "link-underline": (
+ "underline-color": (
property: (
- --#{$prefix}link-underline: null,
- --#{$prefix}link-underline-opacity: 1,
- text-decoration-color: color-mix(in srgb, var(--#{$prefix}link-underline) calc(var(--#{$prefix}link-underline-opacity) * 100%), transparent),
+ --#{$prefix}underline: null,
+ --#{$prefix}underline-opacity: 1,
+ text-decoration-color: color-mix(in srgb, var(--#{$prefix}underline) calc(var(--#{$prefix}underline-opacity) * 100%), transparent),
),
- class: link-underline,
+ class: underline,
values: map.merge(
$utilities-links-underline,
(
@@ -1078,9 +1086,9 @@ $utilities: map.merge(
)
)
),
- "link-underline-opacity": (
- property: (--#{$prefix}link-underline-opacity: null),
- class: link-underline-opacity,
+ "underline-opacity": (
+ property: (--#{$prefix}underline-opacity: null),
+ class: underline-opacity,
state: hover,
values: (
0: 0,
@@ -1126,12 +1134,20 @@ $utilities: map.merge(
)
),
"subtle-background-color": (
- property: background-color,
+ property: (
+ --#{$prefix}bg: null,
+ --#{$prefix}bg-opacity: 1,
+ background-color: color-mix(in srgb, var(--#{$prefix}bg) calc(var(--#{$prefix}bg-opacity) * 100%), transparent),
+ ),
class: bg,
values: $utilities-bg-subtle
),
"muted-background-color": (
- property: background-color,
+ property: (
+ --#{$prefix}bg: null,
+ --#{$prefix}bg-opacity: 1,
+ background-color: color-mix(in srgb, var(--#{$prefix}bg) calc(var(--#{$prefix}bg-opacity) * 100%), transparent),
+ ),
class: bg,
values: $utilities-bg-muted
),
@@ -1194,7 +1210,21 @@ $utilities: map.merge(
property: border-radius,
class: rounded,
values: (
- null: var(--#{$prefix}border-radius),
+ null: var(--#{$prefix}rounded-size, var(--#{$prefix}border-radius)),
+ 0: 0,
+ 1: var(--#{$prefix}border-radius-sm),
+ 2: var(--#{$prefix}border-radius),
+ 3: var(--#{$prefix}border-radius-lg),
+ 4: var(--#{$prefix}border-radius-xl),
+ 5: var(--#{$prefix}border-radius-xxl),
+ circle: 50%,
+ pill: var(--#{$prefix}border-radius-pill)
+ )
+ ),
+ "rounded-size": (
+ property: --#{$prefix}rounded-size,
+ class: rounded-size,
+ values: (
0: 0,
1: var(--#{$prefix}border-radius-sm),
2: var(--#{$prefix}border-radius),
@@ -1209,7 +1239,7 @@ $utilities: map.merge(
property: border-top-left-radius border-top-right-radius,
class: rounded-top,
values: (
- null: var(--#{$prefix}border-radius),
+ null: var(--#{$prefix}rounded-size, var(--#{$prefix}border-radius)),
0: 0,
1: var(--#{$prefix}border-radius-sm),
2: var(--#{$prefix}border-radius),
@@ -1224,7 +1254,7 @@ $utilities: map.merge(
property: border-start-end-radius border-end-end-radius,
class: rounded-end,
values: (
- null: var(--#{$prefix}border-radius),
+ null: var(--#{$prefix}rounded-size, var(--#{$prefix}border-radius)),
0: 0,
1: var(--#{$prefix}border-radius-sm),
2: var(--#{$prefix}border-radius),
@@ -1239,7 +1269,7 @@ $utilities: map.merge(
property: border-bottom-right-radius border-bottom-left-radius,
class: rounded-bottom,
values: (
- null: var(--#{$prefix}border-radius),
+ null: var(--#{$prefix}rounded-size, var(--#{$prefix}border-radius)),
0: 0,
1: var(--#{$prefix}border-radius-sm),
2: var(--#{$prefix}border-radius),
@@ -1254,7 +1284,7 @@ $utilities: map.merge(
property: border-end-start-radius border-start-start-radius,
class: rounded-start,
values: (
- null: var(--#{$prefix}border-radius),
+ null: var(--#{$prefix}rounded-size, var(--#{$prefix}border-radius)),
0: 0,
1: var(--#{$prefix}border-radius-sm),
2: var(--#{$prefix}border-radius),
From 28fe250e4b18757d9fd939aba28b32b305f5c50e Mon Sep 17 00:00:00 2001
From: mrholek
Date: Wed, 2 Sep 2026 00:43:57 +0200
Subject: [PATCH 3/3] fix(docs): compile the CSS the utility tables read, and
order narrow prefixes first
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The docs job runs `npm ci && npm run docs-build` and never rebuilds the
stylesheet, while 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.
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
---
docs/src/content/docs/utilities/border-radius.mdx | 2 +-
docs/src/content/docs/utilities/link.mdx | 2 +-
package.json | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/docs/src/content/docs/utilities/border-radius.mdx b/docs/src/content/docs/utilities/border-radius.mdx
index 240e0ecec..e65a13ca8 100644
--- a/docs/src/content/docs/utilities/border-radius.mdx
+++ b/docs/src/content/docs/utilities/border-radius.mdx
@@ -2,7 +2,7 @@
title: "Bootstrap 6 Border Radius"
name: "Border radius"
description: "Round the corners of an element with our border-radius utilities, from a subtle radius to a full pill."
-utility: ["rounded", "rounded-size"]
+utility: ["rounded-size", "rounded"]
---
## Examples
diff --git a/docs/src/content/docs/utilities/link.mdx b/docs/src/content/docs/utilities/link.mdx
index 3ecda81ae..395a68349 100644
--- a/docs/src/content/docs/utilities/link.mdx
+++ b/docs/src/content/docs/utilities/link.mdx
@@ -2,7 +2,7 @@
title: "Bootstrap 6 Link"
name: "Link"
description: "Link utilities are used to stylize your anchors to adjust their color, opacity, underline offset, underline color, and more."
-utility: ["link-opacity", "underline", "underline-offset", "underline-opacity", "underline-thickness"]
+utility: ["link-opacity", "underline-offset", "underline-opacity", "underline-thickness", "underline"]
---
import { getData } from '@coreui/astro-docs/data'
diff --git a/package.json b/package.json
index 4b658d618..9867ca2b0 100644
--- a/package.json
+++ b/package.json
@@ -57,7 +57,7 @@
"js-test-jquery": "cross-env JQUERY=true vitest run --config js/tests/vitest.config.mts",
"lint": "npm-run-all --aggregate-output --continue-on-error --parallel js-lint js-typecheck js-test-default-type css-lint lockfile-lint",
"docs": "npm-run-all docs-build docs-lint",
- "docs-build": "astro build --root docs",
+ "docs-build": "npm run css-compile && astro build --root docs",
"docs-compile": "npm run docs-build",
"docs-vnu": "node build/vnu-jar.mjs",
"docs-lint": "npm run docs-vnu",