Structural
Overview
Structural modifiers let you apply utility styles conditionally based on an element's position within its parent. They wrap utility declarations in CSS structural pseudo-class selectors like :first-child, :last-child, and :nth-child(), generating variant utility classes that target elements by position.
Why Use Structural Modifiers?
Structural modifiers help you:
- Style list items differently: Apply unique styles to the first, last, or alternating items
- Create zebra striping: Use odd/even modifiers for table rows or list items
- Remove redundant borders: Remove top/bottom borders on first/last items
- Handle edge cases: Style only-child or empty elements differently
- Build dynamic layouts: Adapt styling based on element position without JavaScript
useFirstModifier
The useFirstModifier() function creates a modifier that applies styles to the first child element.
import { styleframe } from "styleframe";
import { useBorderWidthUtility, useBorderRadiusUtility } from "@styleframe/theme";
import { useFirstModifier } from "@styleframe/theme";
const s = styleframe();
const first = useFirstModifier(s);
useBorderWidthUtility(s, {
0: '0',
}, [first]);
useBorderRadiusUtility(s, {
'top-md': '0.375rem 0.375rem 0 0',
}, [first]);
export default s;
._border-width\:0 { border-width: 0; }
._first\:border-width\:0 {
&:first-child { border-width: 0; }
}
._first\:border-radius\:top-md {
&:first-child { border-radius: 0.375rem 0.375rem 0 0; }
}
<ul>
<li class="_first:border-width:0 _first:border-radius:top-md">First item</li>
<li>Middle item</li>
<li>Last item</li>
</ul>
CSS Selector
| Modifier Name | CSS Selector |
|---|---|
first | &:first-child |
useLastModifier
The useLastModifier() function creates a modifier that applies styles to the last child element.
import { styleframe } from "styleframe";
import { useBorderWidthUtility } from "@styleframe/theme";
import { useLastModifier } from "@styleframe/theme";
const s = styleframe();
const last = useLastModifier(s);
useBorderWidthUtility(s, {
0: '0',
}, [last]);
export default s;
._last\:border-width\:0 {
&:last-child { border-width: 0; }
}
<ul>
<li>First item</li>
<li>Middle item</li>
<li class="_last:border-width:0">Last item (no bottom border)</li>
</ul>
CSS Selector
| Modifier Name | CSS Selector |
|---|---|
last | &:last-child |
useOnlyModifier
The useOnlyModifier() function creates a modifier that applies styles when an element is the only child.
CSS Selector
| Modifier Name | CSS Selector |
|---|---|
only | &:only-child |
useOddModifier
The useOddModifier() function creates a modifier that applies styles to odd-numbered children (1st, 3rd, 5th, etc.).
import { styleframe } from "styleframe";
import { useBackgroundColorUtility } from "@styleframe/theme";
import { useOddModifier, useEvenModifier } from "@styleframe/theme";
const s = styleframe();
const odd = useOddModifier(s);
const even = useEvenModifier(s);
useBackgroundColorUtility(s, {
white: '#ffffff',
light: '#f8f9fa',
}, [odd, even]);
export default s;
._background-color\:white { background-color: #ffffff; }
._background-color\:light { background-color: #f8f9fa; }
._odd\:background-color\:light {
&:nth-child(odd) { background-color: #f8f9fa; }
}
._even\:background-color\:white {
&:nth-child(even) { background-color: #ffffff; }
}
<!-- Zebra-striped table -->
<table>
<tr class="_odd:background-color:light _even:background-color:white">
<td>Row 1</td>
</tr>
<tr class="_odd:background-color:light _even:background-color:white">
<td>Row 2</td>
</tr>
<tr class="_odd:background-color:light _even:background-color:white">
<td>Row 3</td>
</tr>
</table>
CSS Selector
| Modifier Name | CSS Selector |
|---|---|
odd | &:nth-child(odd) |
useEvenModifier
The useEvenModifier() function creates a modifier that applies styles to even-numbered children (2nd, 4th, 6th, etc.).
CSS Selector
| Modifier Name | CSS Selector |
|---|---|
even | &:nth-child(even) |
useFirstOfTypeModifier
The useFirstOfTypeModifier() function creates a modifier that applies styles to the first element of its type among siblings.
CSS Selector
| Modifier Name | CSS Selector |
|---|---|
first-of-type | &:first-of-type |
useLastOfTypeModifier
The useLastOfTypeModifier() function creates a modifier that applies styles to the last element of its type among siblings.
CSS Selector
| Modifier Name | CSS Selector |
|---|---|
last-of-type | &:last-of-type |
useOnlyOfTypeModifier
The useOnlyOfTypeModifier() function creates a modifier that applies styles when an element is the only one of its type among siblings.
CSS Selector
| Modifier Name | CSS Selector |
|---|---|
only-of-type | &:only-of-type |
useEmptyModifier
The useEmptyModifier() function creates a modifier that applies styles to elements with no children.
import { styleframe } from "styleframe";
import { useDisplayUtility } from "@styleframe/theme";
import { useEmptyModifier } from "@styleframe/theme";
const s = styleframe();
const empty = useEmptyModifier(s);
useDisplayUtility(s, {
none: 'none',
}, [empty]);
export default s;
._display\:none { display: none; }
._empty\:display\:none {
&:empty { display: none; }
}
<!-- Hide container when it has no content -->
<div class="_empty:display:none">
<!-- Will be hidden when empty -->
</div>
CSS Selector
| Modifier Name | CSS Selector |
|---|---|
empty | &:empty |
useStructuralModifiers
The useStructuralModifiers() function registers all structural modifiers at once and returns them as a destructurable object.
Returned Modifiers
| Key | Modifier Name | CSS Selector |
|---|---|---|
first | first | &:first-child |
last | last | &:last-child |
only | only | &:only-child |
odd | odd | &:nth-child(odd) |
even | even | &:nth-child(even) |
firstOfType | first-of-type | &:first-of-type |
lastOfType | last-of-type | &:last-of-type |
onlyOfType | only-of-type | &:only-of-type |
empty | empty | &:empty |
Examples
List with Border Separators
import { styleframe } from "styleframe";
import {
useBorderWidthUtility,
useBorderRadiusUtility,
usePaddingUtility,
} from "@styleframe/theme";
import { useStructuralModifiers } from "@styleframe/theme";
const s = styleframe();
const { first, last } = useStructuralModifiers(s);
useBorderWidthUtility(s, {
'top-0': '0',
}, [first]);
useBorderRadiusUtility(s, {
'top-md': '0.375rem 0.375rem 0 0',
'bottom-md': '0 0 0.375rem 0.375rem',
0: '0',
}, [first, last]);
export default s;
<ul class="list">
<li class="_first:border-radius:top-md _last:border-radius:bottom-md">Item 1</li>
<li class="_first:border-radius:top-md _last:border-radius:bottom-md">Item 2</li>
<li class="_first:border-radius:top-md _last:border-radius:bottom-md">Item 3</li>
</ul>
Zebra-Striped Data Table
import { styleframe } from "styleframe";
import { useBackgroundColorUtility } from "@styleframe/theme";
import { useOddModifier, useHoverModifier } from "@styleframe/theme";
const s = styleframe();
const odd = useOddModifier(s);
const hover = useHoverModifier(s);
useBackgroundColorUtility(s, {
light: '#f8f9fa',
highlight: '#e9ecef',
}, [odd, hover]);
export default s;
<table>
<tbody>
<tr class="_odd:background-color:light _hover:background-color:highlight">
<td>Alice</td><td>alice@example.com</td>
</tr>
<tr class="_odd:background-color:light _hover:background-color:highlight">
<td>Bob</td><td>bob@example.com</td>
</tr>
<tr class="_odd:background-color:light _hover:background-color:highlight">
<td>Charlie</td><td>charlie@example.com</td>
</tr>
</tbody>
</table>
Best Practices
- Use first/last for border cleanup: Remove redundant borders on first and last items in lists
- Prefer odd/even for zebra striping: More maintainable than manually alternating classes
- Combine with hover: Add hover effects alongside structural modifiers for interactive lists and tables
- Use empty for conditional display: Hide containers that have no content without JavaScript
- Use first-of-type/last-of-type carefully: They match by element type, not by class, which can produce unexpected results in mixed-content containers
FAQ
:first-child matches the first element regardless of its type. :first-of-type matches the first element of a specific type (e.g., the first <p> among siblings). Use first-child for general positioning and first-of-type when you need type-specific targeting.:empty matches elements that have no children, including text nodes. An element with only whitespace text was previously not considered empty, but modern CSS treats elements containing only whitespace as empty. However, behavior may vary across browsers.[first, hover] generates _first:property:value, _hover:property:value, and _first:hover:property:value for first-child hover states.