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, ` + +
+ + +
`} /> + +## 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 +`} /> + +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
+ + +
+
With .min-w-0 the same line truncates instead
+ +
`} /> ## 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
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",