sdxc

[ @sdxc/u/state ]

precededBy

Sugar over when(":is({selector}) ~ &", input), the backward-looking twin of hasSibling().

import { precededBy } from "@sdxc/u/state";

Quick reference

Every documented call, beside the CSS it emits.

CallCSS
u.precededBy("input:checked", u.border("brand.solid"))css({ ":is(input:checked) ~ &": { borderColor: "var(--ui-brand-bg-solid)" } })
u.precededBy("input:focus-visible", u.outline({ color: "brand", offset: 2 }))css({ ":is(input:focus-visible) ~ &": { outlineColor: "...", outlineOffset: "2px" } })

Examples

Basic example

Pass a selector to precededBy() to style an element by a sibling that comes before it. Tick the tasks:

<ul mix={[vstack({ gap: 2 }), text("sm")]}>
	<li>
		<label mix={[hstack({ gap: 2, align: "center" })]}>
			<input type="checkbox" checked mix={[accent()]} />
			<span
				mix={[
					fg(),
					precededBy("input:checked", [textDecoration("line-through"), fg("neutral.muted")]),
				]}
			>
				Write the tests
			</span>
		</label>
	</li>
	<li>
		<label mix={[hstack({ gap: 2, align: "center" })]}>
			<input type="checkbox" mix={[accent()]} />
			<span
				mix={[
					fg(),
					precededBy("input:checked", [textDecoration("line-through"), fg("neutral.muted")]),
				]}
			>
				Ship the release
			</span>
		</label>
	</li>
</ul>

Custom radio buttons

Hide each radio and paint the text after it with precededBy(), one pair per label. Click a segment, or tab in and use the arrow keys:

<fieldset
	mix={[hstack({ gap: 1 }), p(1), rounded("lg"), border({ color: "neutral.border", width: 1 })]}
>
	<label>
		<input type="radio" name="view" checked mix={[visuallyHidden()]} />
		<span
			mix={[
				fg("neutral.muted"),
				cursor("pointer"),
				precededBy("input:checked", [bg("brand.solid"), fg("brand.onSolid")]),
				precededBy("input:focus-visible", outline({ color: "brand", offset: 2 })),
				...,
			]}
		>
			List
		</span>
	</label>
	<label>
		<input type="radio" name="view" mix={[visuallyHidden()]} />
		<span
			mix={[
				fg("neutral.muted"),
				cursor("pointer"),
				precededBy("input:checked", [bg("brand.solid"), fg("brand.onSolid")]),
				precededBy("input:focus-visible", outline({ color: "brand", offset: 2 })),
				...,
			]}
		>
			Grid
		</span>
	</label>
</fieldset>

Responsive design

Wrap the call in a container query to scope it to one width.

u.at("md", u.precededBy("input:checked", u.border("brand.solid")))

Customizing your theme

Customizing colors

Define a palette as --ui-color-{name}-{50..950} and map a tone onto it with the --ui-{tone}-* variables, the way theme.css maps the built-in ones:

:root {
	--ui-color-info-50: oklch(0.97 0.02 230);
	--ui-color-info-200: oklch(0.88 0.06 230);
	--ui-color-info-500: oklch(0.62 0.16 230);
	--ui-color-info-600: oklch(0.52 0.18 230);
	--ui-color-info-900: oklch(0.3 0.1 230);

	--ui-info-bg-tint: var(--ui-color-info-50);
	--ui-info-bg-solid: var(--ui-color-info-600);
	--ui-info-fg: var(--ui-color-info-600);
	--ui-info-fg-muted: var(--ui-color-info-500);
	--ui-info-fg-emphasis: var(--ui-color-info-900);
	--ui-info-fg-on-solid: white;
	--ui-info-border: var(--ui-color-info-200);
	--ui-info-border-strong: var(--ui-color-info-600);
	--ui-info-ring: var(--ui-color-info-500);
}
declare module "@sdxc/u" {
	interface ColorPalettes {
		info: true;
	}
	interface SemanticTones {
		info: true;
	}
}

The new name is then accepted wherever the scale is:

bg("info")

Signature

u.precededBy(selector: string, input: UtilityInput<Node>)

Written by Sergio Xalambrí. Follow @sergiodxa for new packages, or sponsor the work.