sdxc

[ @sdxc/ui ]

SelectionIndicator

A small marker that stays invisible, while still reserving its own layout slot.

Sort packages by
Nothing shifts sideways as the marker moves.
View code
<div mix={[vstack({ gap: 1, align: "stretch" }), on<HTMLDivElement, "change">("change", readOrder)]}>
	<Header id="preview-sort-heading">Sort packages by</Header>
	<div role="group" aria-labelledby="preview-sort-heading" mix={[vstack({ gap: 0, align: "stretch" })]}>
		{orders.map((order) => (
			<label
				key={order.value}
				mix={[
					hstack({ gap: 2, align: "center" }),
					p(2),
					css({ cursor: "pointer", borderRadius: "var(--ui-radius-md)" }),
				]}
			>
				<SelectionIndicator aria-selected={order.value === selected ? "true" : "false"}>
					<CheckIcon />
				</SelectionIndicator>
				<input
					type="radio"
					name="preview-order"
					value={order.value}
					checked={order.value === selected}
					mix={[ariaChecked(), visuallyHidden()]}
				/>
				{order.label}
			</label>
		))}
	</div>
	<Text>Nothing shifts sideways as the marker moves.</Text>
</div>

Installation

An ordinary dependency: install it, import the theme once, and import the component where it is used.

npm add @sdxc/ui remix
import "@sdxc/ui/theme.css";

Usage

import { SelectionIndicator } from "@sdxc/ui";

Renders a marker that stays present in the layout at all times but only becomes visible once its host carries aria-selected="true" or a truthy aria-current, so sibling content never reflows on selection change.

Examples

Tab underline

View code
let activeTab = "billing";

<Tabs>
	<Tabs.List aria-label="Settings sections">
		{sections.map((section) => (
			<Tabs.Tab
				key={section.id}
				href={"#" + section.id}
				aria-selected={activeTab === section.id ? "true" : "false"}
				mix={[
					relative(),
					on<HTMLAnchorElement, "click">("click", (event) => {
						event.preventDefault();
						activeTab = section.id;
						void handle.update();
					}),
				]}
			>
				{section.label}
				<SelectionIndicator
					aria-selected={activeTab === section.id ? "true" : "false"}
					mix={[absolute(), insIs(0), insIe(0), insBe("-0.125rem"), is("auto"), bs("0.125rem"), bg("brand.solid")]}
				/>
			</Tabs.Tab>
		))}
	</Tabs.List>
</Tabs>

Props

Read from the component's own types, so every prop, every default and every allowed value is listed.

Also accepts everything in TagProps<"div">.

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