},
{
"path": "./dist/css/bootstrap-utilities.css",
- "maxSize": "16.75 kB"
+ "maxSize": "17.75 kB"
},
{
"path": "./dist/css/bootstrap-utilities.min.css",
- "maxSize": "16.0 kB"
+ "maxSize": "16.75 kB"
},
{
"path": "./dist/css/bootstrap.css",
- "maxSize": "46.75 kB"
+ "maxSize": "47.75 kB"
},
{
"path": "./dist/css/bootstrap.min.css",
- "maxSize": "44.0 kB"
+ "maxSize": "44.75 kB"
},
{
"path": "./dist/js/bootstrap.bundle.js",
"column-gap-9"
]
},
+ "space-x": {
+ "class": "space-x",
+ "property": "margin-inline-end",
+ "classes": [
+ "space-x-0",
+ "space-x-1",
+ "space-x-2",
+ "space-x-3",
+ "space-x-4",
+ "space-x-5",
+ "space-x-6",
+ "space-x-7",
+ "space-x-8",
+ "space-x-9"
+ ]
+ },
+ "space-y": {
+ "class": "space-y",
+ "property": "margin-block-end",
+ "classes": [
+ "space-y-0",
+ "space-y-1",
+ "space-y-2",
+ "space-y-3",
+ "space-y-4",
+ "space-y-5",
+ "space-y-6",
+ "space-y-7",
+ "space-y-8",
+ "space-y-9"
+ ]
+ },
+ "divide-x": {
+ "class": "divide-x",
+ "property": "border-inline-start",
+ "classes": [
+ "divide-x",
+ "divide-x-0"
+ ]
+ },
+ "divide-y": {
+ "class": "divide-y",
+ "property": "border-block-start",
+ "classes": [
+ "divide-y",
+ "divide-y-0"
+ ]
+ },
"font-family": {
"class": "font",
"property": "font-family",
values: $spacers
),
// scss-docs-end utils-spacing
+ // scss-docs-start utils-space
+ "space-x": (
+ responsive: true,
+ property: margin-inline-end,
+ class: space-x,
+ child-selector: "> :not(:last-child)",
+ values: $spacers
+ ),
+ "space-y": (
+ responsive: true,
+ property: margin-block-end,
+ class: space-y,
+ child-selector: "> :not(:last-child)",
+ values: $spacers
+ ),
+ // scss-docs-end utils-space
+ // scss-docs-start utils-divide
+ "divide-x": (
+ responsive: true,
+ property: border-inline-start,
+ class: divide-x,
+ child-selector: "> :not(:first-child)",
+ values: (
+ null: var(--border-width) var(--border-style) var(--border-color),
+ 0: 0,
+ )
+ ),
+ "divide-y": (
+ responsive: true,
+ property: border-block-start,
+ class: divide-y,
+ child-selector: "> :not(:first-child)",
+ values: (
+ null: var(--border-width) var(--border-style) var(--border-color),
+ 0: 0,
+ )
+ ),
+ // scss-docs-end utils-divide
// Text
// scss-docs-start utils-font-family
"font-family": (
// - class: .class
// - attr-starts: [class^="class"]
// - attr-includes: [class*="class"]
+// - Utilities can target children via `child-selector`, wrapped in :where() for zero specificity
// - Utilities can generate regular CSS properties and CSS custom properties
// - Utilities can be responsive or not
// - Utilities can have state variants (e.g., hover, focus, active)
}
// Warn on unknown keys (likely typos)
- $valid-keys: property, values, class, selector, responsive, print, important, state, variables;
+ $valid-keys: property, values, class, selector, responsive, print, important, state, variables, child-selector;
@each $key in map.keys($utility) {
@if not list.index($valid-keys, $key) {
@warn "Unknown utility key `#{$key}` found. Valid keys are: #{$valid-keys}";
// @debug $properties;
// @debug $values;
- #{$selector} {
+ // Apply child-selector wrapping if present (wraps in :where() for zero specificity)
+ $child-sel: null;
+ @if map.has-key($utility, child-selector) {
+ $child-sel: map.get($utility, child-selector);
+ }
+
+ $final-selector: $selector;
+ @if $child-sel {
+ $final-selector: ":where(#{$selector} #{$child-sel})";
+ }
+
+ #{$final-selector} {
// Generate CSS custom properties (variables) if provided
// Variables receive the current utility value, then properties reference them
@if map.has-key($utility, variables) {
// Generate state variants
@if $state != () {
@each $state-variant in $state {
- #{$selector}-#{$state-variant}:#{$state-variant} {
+ $state-selector: "#{$selector}-#{$state-variant}:#{$state-variant}";
+ @if $child-sel {
+ $state-selector: ":where(#{$state-selector} #{$child-sel})";
+ }
+
+ #{$state-selector} {
// Generate CSS custom properties (variables) if provided
@if map.has-key($utility, variables) {
$variables: map.get($utility, variables);
pages:
- title: Margin
- title: Padding
+ - title: Space
- group: Type
pages:
- title: Font family
- title: Border
- title: Border color
- title: Border radius
+ - title: Divide
- group: Interactions
pages:
- title: Pointer events
const classStyles: Record<string, string[]> = {};
classNames.forEach(className => {
- // Match ONLY single class selectors: .classname { declarations }
+ // Match class selectors, including :where() wrapped child-selector utilities
const escapedClass = className.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
- const selectorRegex = new RegExp(`(?:^|\\n)\\s*\\.${escapedClass}\\s*\\{([^}]+)\\}`, 'gm');
+ const selectorRegex = new RegExp(
+ `(?:^|\\n)\\s*(?::where\\()?\\s*\\.${escapedClass}(?:\\s[^{]*)?\\)?\\s*\\{([^}]+)\\}`,
+ 'gm'
+ );
let match;
let foundDeclarations: string[] = [];
- Added `auto`, `min-content`, `max-content`, and `fit-content` to `width` and `height` utilities.
- **Flex & Grid utilities:**
- Added `.place-items` and `.justify-items` utilities.
+- **Utilities API:** Added new `child-selector` option that enables parent-to-child selector patterns wrapped in `:where()` for zero specificity. Any CSS child/descendant selector can be used.
+- **Space utilities:** Added `.space-x-*` and `.space-y-*` utilities for margin-based spacing between direct children of an element. Responsive variants included.
+- **Divide utilities:** Added `.divide-x` and `.divide-y` utilities for adding borders between direct children of an element, using existing border CSS variables. Responsive variants included.
### Docs
| [`property`](#property) | **Required** | – | Name of the property, this can be a string or an array of strings (e.g., horizontal paddings or margins). |
| [`values`](#values) | **Required** | – | List of values, or a map if you don't want the class name to be the same as the value. If `null` is used as map key, `class` is not prepended to the class name. |
| [`selector`](#selector) | Optional | `class` | Type of CSS selector in the generated CSS ruleset. Can be `class`, `attr-starts`, or `attr-includes`. |
+| [`child-selector`](#child-selector) | Optional | null | A child/descendant selector appended to the utility's selector, wrapped in `:where()` for zero specificity. Use to target children instead of the element itself. |
| [`class`](#class) | Optional | null | Name of the generated class. If not provided and `property` is an array of strings, `class` will default to the first element of the `property` array. If not provided and `property` is a string, the `values` keys are used for the `class` names. |
| [`css-var`](#css-variable-utilities) | Optional | `false` | Boolean to generate CSS variables instead of CSS rules. |
| [`css-variable-name`](#css-variable-utilities) | Optional | null | Custom un-prefixed name for the CSS variable inside the ruleset. |
.ratio-21x9 { --bs-ratio: 21 / 9; }
```
+### `child-selector`
+
+Use the `child-selector` option to apply a CSS property to children of the element with the utility class rather than the element itself. The generated selector is wrapped in `:where()` for zero specificity, making it easy to override. This powers our built-in [`space-x/y`]([[docsref:/utilities/space]]) and [`divide-x/y`]([[docsref:/utilities/divide]]) utilities.
+
+For example, our `space-x` utility uses `child-selector` to add horizontal spacing between direct children:
+
+```scss
+$utilities: (
+ "space-x": (
+ property: margin-inline-end,
+ class: space-x,
+ child-selector: "> :not(:last-child)",
+ values: (
+ 1: .25rem,
+ 2: .5rem,
+ 3: 1rem,
+ )
+ )
+);
+```
+
+Output:
+
+```css
+:where(.space-x-1 > :not(:last-child)) { margin-inline-end: .25rem; }
+:where(.space-x-2 > :not(:last-child)) { margin-inline-end: .5rem; }
+:where(.space-x-3 > :not(:last-child)) { margin-inline-end: 1rem; }
+```
+
+The `child-selector` value can be any valid CSS selector. For example, you could create a striped row utility using `:nth-child()`:
+
+```scss
+$utilities: (
+ "striped-bg": (
+ property: background-color,
+ class: striped,
+ child-selector: "> :nth-child(odd)",
+ values: (
+ null: var(--bs-tertiary-bg),
+ )
+ )
+);
+```
+
+Output:
+
+```css
+:where(.striped > :nth-child(odd)) { background-color: var(--bs-tertiary-bg); }
+```
+
+Or target all direct children with `> *`:
+
+```scss
+$utilities: (
+ "child-rounded": (
+ property: border-radius,
+ class: child-rounded,
+ child-selector: "> *",
+ values: (
+ null: var(--bs-border-radius),
+ 0: 0,
+ )
+ )
+);
+```
+
+Output:
+
+```css
+:where(.child-rounded > *) { border-radius: var(--bs-border-radius); }
+:where(.child-rounded-0 > *) { border-radius: 0; }
+```
+
### `class`
Use the `class` option to change the class prefix used in the compiled CSS. For example, to change from `.opacity-*` to `.o-*`:
--- /dev/null
+---
+title: Divide
+description: Divide content with borders between direct children of an element. Supports horizontal and vertical dividers with responsive variants.
+toc: true
+mdn: https://developer.mozilla.org/en-US/docs/Web/CSS/border
+utility:
+ - divide-x
+ - divide-y
+---
+
+import { getData } from '@libs/data'
+
+## Example
+
+Rather than adding border classes to individual children, use `.divide-x` or `.divide-y` on the parent.
+
+<Example class="d-flex align-items-center justify-content-center text-center" code={`
+ <div class="d-flex divide-x">
+ <div class="px-3 py-2">Item 1</div>
+ <div class="px-3 py-2">Item 2</div>
+ <div class="px-3 py-2">Item 3</div>
+ </div>`} />
+
+The selectors generated for divider utilities are wrapped in `:where()` for zero specificity, making them easy to override with additional utilities or custom CSS.
+
+## Horizontal
+
+Use `.divide-x` to add vertical border lines between horizontally arranged children:
+
+<Example class="d-flex align-items-center justify-content-center text-center" code={`
+ <div class="d-flex divide-x rounded-3 border">
+ <div class="px-4 py-3">One</div>
+ <div class="px-4 py-3">Two</div>
+ <div class="px-4 py-3">Three</div>
+ </div>`} />
+
+## Vertical
+
+Use `.divide-y` to add horizontal border lines between vertically stacked children:
+
+<Example class="d-flex align-items-center justify-content-center text-center" code={`
+ <div class="divide-y rounded-3 border d-inline-block">
+ <div class="px-4 py-3">One</div>
+ <div class="px-4 py-3">Two</div>
+ <div class="px-4 py-3">Three</div>
+ </div>`} />
+
+### Removing dividers
+
+Use `.divide-x-0` or `.divide-y-0` to remove dividers, which is particularly useful at specific breakpoints:
+
+```html
+<div class="divide-y divide-y-md-0 divide-x-md">
+ ...
+</div>
+```
+
+### How it works
+
+Divide utilities apply `border-inline-start` or `border-block-start` to every direct child except the first via the selector `:where(.divide-y > :not(:first-child))`. The `:where()` wrapper keeps specificity at zero. Border styling is controlled by the existing `--border-width`, `--border-style`, and `--border-color` CSS variables.
+
+```css
+:where(.divide-y > :not(:first-child)) {
+ border-block-start: var(--bs-border-width) var(--bs-border-style) var(--bs-border-color);
+}
+```
+
+## CSS
+
+### Sass utilities API
+
+Divide utilities are declared in our utilities API in `scss/_utilities.scss`. [Learn how to use the utilities API.]([[docsref:/utilities/api#using-the-api]])
+
+```scss
+// scss-docs-start utils-divide
+"divide-x": (
+ responsive: true,
+ property: border-inline-start,
+ class: divide-x,
+ child-selector: "> :not(:first-child)",
+ values: (
+ null: var(--border-width) var(--border-style) var(--border-color),
+ 0: 0,
+ )
+),
+"divide-y": (
+ responsive: true,
+ property: border-block-start,
+ class: divide-y,
+ child-selector: "> :not(:first-child)",
+ values: (
+ null: var(--border-width) var(--border-style) var(--border-color),
+ 0: 0,
+ )
+),
+// scss-docs-end utils-divide
+```
--- /dev/null
+---
+title: Space
+description: Control the margin between direct children of an element in non-flex and non-grid layouts.
+toc: true
+mdn: https://developer.mozilla.org/en-US/docs/Web/CSS/margin
+utility:
+ - space-x
+ - space-y
+---
+
+import { getData } from '@libs/data'
+
+## Example
+
+Unlike [gap utilities]([[docsref:/utilities/gap]]) which require flexbox or grid, space utilities work on any parent element by applying margins to children.
+
+<Example class="d-flex align-items-center justify-content-center gap-4 text-center" code={`
+ <div class="d-flex space-x-3 p-3 bd-pattern-diagonal rounded-3">
+ <div class="p-3 bg-subtle-accent fg-accent rounded-3">Item 1</div>
+ <div class="p-3 bg-subtle-accent fg-accent rounded-3">Item 2</div>
+ <div class="p-3 bg-subtle-accent fg-accent rounded-3">Item 3</div>
+ </div>`} />
+
+The selectors generated for space utilities are wrapped in `:where()` for zero specificity, making them easy to override with additional utilities or custom CSS.
+
+## Notation
+
+Space utilities that apply to all breakpoints, from `xs` to `2xl`, have no breakpoint abbreviation in them. This is because those classes are applied from `min-width: 0` and up, and thus are not bound by a media query. The remaining breakpoints, however, do include a breakpoint abbreviation.
+
+The classes are named using the format `{property}-{size}` for `xs` and `{property}-{breakpoint}-{size}` for `sm`, `md`, `lg`, `xl`, and `2xl`.
+
+Where *property* is one of:
+
+- `space-x` - for classes that set horizontal spacing via `margin-inline-end`
+- `space-y` - for classes that set vertical spacing via `margin-block-end`
+
+Where *size* is one of:
+
+- `0` - for classes that eliminate the spacing by setting it to `0`
+- `1` - (by default) for classes that set the spacing to `$spacer * .25`
+- `2` - (by default) for classes that set the spacing to `$spacer * .5`
+- `3` - (by default) for classes that set the spacing to `$spacer`
+- `4` - (by default) for classes that set the spacing to `$spacer * 1.5`
+- `5` - (by default) for classes that set the spacing to `$spacer * 3`
+
+(You can add more sizes by adding entries to the `$spacers` Sass map variable.)
+
+## Horizontal
+
+Use `space-x-*` utilities to control the horizontal space between children:
+
+<Example showMarkup={false} class="d-flex align-items-center justify-content-center text-center" code={`
+ <div class="d-flex space-x-3 bd-pattern-diagonal rounded-3">
+ <div class="p-3 bg-subtle-accent fg-accent rounded-3">Item 1</div>
+ <div class="p-3 bg-subtle-accent fg-accent rounded-3">Item 2</div>
+ <div class="p-3 bg-subtle-accent fg-accent rounded-3">Item 3</div>
+ </div>`} />
+
+```html
+<div class="d-flex space-x-3">
+ <div>Item 1</div>
+ <div>Item 2</div>
+ <div>Item 3</div>
+</div>
+```
+
+## Vertical
+
+Use `space-y-*` utilities to control the vertical space between children:
+
+<Example showMarkup={false} class="d-flex align-items-center justify-content-center text-center" code={`
+ <div class="space-y-3 bd-pattern-diagonal rounded-3 d-inline-block p-3">
+ <div class="p-3 bg-subtle-accent fg-accent rounded-3">Item 1</div>
+ <div class="p-3 bg-subtle-accent fg-accent rounded-3">Item 2</div>
+ <div class="p-3 bg-subtle-accent fg-accent rounded-3">Item 3</div>
+ </div>`} />
+
+```html
+<div class="space-y-3">
+ <div>Item 1</div>
+ <div>Item 2</div>
+ <div>Item 3</div>
+</div>
+```
+
+## How it works
+
+Space utilities apply `margin-inline-end` or `margin-block-end` to every direct child except the last one via the selector `:where(.space-x-3 > :not(:last-child))`. The `:where()` wrapper keeps specificity at zero, so you can easily override individual children with standard margin utilities.
+
+```css
+:where(.space-x-3 > :not(:last-child)) {
+ margin-inline-end: 1rem;
+}
+```
+
+## CSS
+
+### Sass utilities API
+
+Space utilities are declared in our utilities API in `scss/_utilities.scss`. [Learn how to use the utilities API.]([[docsref:/utilities/api#using-the-api]])
+
+```scss
+// scss-docs-start utils-space
+"space-x": (
+ responsive: true,
+ property: margin-inline-end,
+ class: space-x,
+ child-selector: "> :not(:last-child)",
+ values: $spacers
+),
+"space-y": (
+ responsive: true,
+ property: margin-block-end,
+ class: space-y,
+ child-selector: "> :not(:last-child)",
+ values: $spacers
+),
+// scss-docs-end utils-space
+```