Skip to main content
xriptxr

Theme Picker

Info:Form
6

A gallery of themes to choose from, each previewed as it will look.

theme picker gallery chooser algorithm invocation select switcher

Live demo

live · @xtyle/astro

ThemePicker

Choosing a theme by looking at it

Every option is an <xtyle-theme-card>, so the preview and the button semantics come from that component. The picker owns the grid and which card is current.

Wired to a scope

xtyle:theme-pick carries the whole invocation, not just a name, so it can be handed straight to a <xtyle-theme-scope> with nothing in between. Pick one and only the panel below changes.

live scope

Default

This panel is a theme scope. The picker hands it an invocation and it derives. The toggle flips this scope, not the page.

sample controls, showing the theme

As a toolbar dropdown

layout="menu" puts the same gallery behind a trigger naming the current choice, for a toolbar with no room to show it outright. The panel is an <xtyle-popover>, so dismissal, placement, and focus return come from the overlay component rather than a hand-rolled dropdown. Everything the gallery takes still applies inside it, swatches included.

With nothing on offer

An empty list says so rather than rendering an empty grid.

Theme Picker is the choosing surface for a set of invocations. Every option is an <xtyle-theme-card>, so the preview, the button semantics, the accessible name, and the selected state all come from that component; the picker owns the grid, which card is current, and reporting the choice.

Turn on swatches and each card gets an <xtyle-theme-swatch> beneath it, pairing the picture with the parts list. It adds no cursor of its own — every card is already a real button, so focus moves through them the way it moves through any group of controls, and there is no bespoke roving tab stop to get wrong. Picking one fires xtyle:theme-pick carrying the whole invocation, not just a name, which is exactly what an <xtyle-theme-scope> needs to apply it: the two compose into a working theme switcher with no glue in between. With no themes it says so rather than rendering an empty grid.

When to use

How this component composes with the rest of the set.

Reach for layout="menu" in a toolbar: the trigger names the current theme and the same gallery opens beneath it, so a switcher is one element rather than a hand-rolled dropdown.
Wire xtyle:theme-pick straight into an <xtyle-theme-scope>: the event carries the whole invocation, so the scope needs no lookup table.
Turn on swatches when the exact colors matter as much as the impression.
Offer the blessed algorithms as the starting set, then append whatever a person has built.
Override the component.theme-picker fill to lay the gallery out differently without touching the cards inside it.

Props

8 props, straight from the manifest.

PropTypeDefaultBindingsDescription
themes PickerTheme[]
html svelte astro
The invocations on offer — each `{ name?, algorithm?, scheme?, knobs?, constraints? }` — as an array in the framework bindings and JSON in the HTML attribute.
value string
html svelte astro
The chosen theme's key: its `name`, or its `algorithm` when unnamed.
label string
html svelte astro
Accessible name for the group of choices.
layout "gallery" | "menu"
gallery menu
gallery
html svelte astro
`gallery` lays the choices out in place; `menu` puts the same gallery behind a trigger showing the current one, for a toolbar with no room to show it outright.
open boolean false
html svelte astro
Start the `menu` layout's panel open. The gallery ignores it, being open by definition.
swatches boolean false
html svelte astro
Show a `<xtyle-theme-swatch>` under each card, so a choice reads as both a picture and its values.
minColWidth string 13rem
html svelte astro
The grid's column floor; the gallery fits as many columns as that allows.
empty string No themes to choose from.
html svelte astro
What to say when `themes` is empty.

Appearance

States

selected

.xtyle-theme-picker__item .xtyle-theme-card--selected

The current choice, carried by the card's own selected state.

Anatomy

The named parts that make up the component, with their selectors.

grid

.xtyle-theme-picker__grid

The gallery. A responsive auto-fill grid whose column floor is min-col-width.

item

.xtyle-theme-picker__item

One choice: its card, and its swatch row when swatches is on.

empty

.xtyle-theme-picker__empty

Shown instead of the grid when there is nothing to choose from.

menu

.xtyle-theme-picker__menu

The layout="menu" shell: an <xtyle-popover> holding the same gallery, with a trigger naming the current choice.

Tokens & coverage

What the component consumes, checked live against what the algorithm produces.

Success:fully covered 6/6 consumed tokens produced default register: 310 tokens

Live coverage check against the xtyle-default register (derive(xtyleDefault, { anchors })coverComponent(manifest, register)). Every token this component consumes must be a key the algorithm produces.

--fg-2 --font-sans --leading-tight --space-2 --space-3 --text-xs

Accessibility

Under layout="menu" the panel is an <xtyle-popover>, so placement, light dismiss, Escape, and focus return are the overlay component's job rather than a bespoke reimplementation.
The gallery is a labelled group of real buttons, so it is reachable and announced without a bespoke cursor.
Each card carries aria-pressed and an accessible name naming its theme, algorithm, and scheme.
Choosing does not move focus, so a keyboard user stays where they were and can keep comparing.
With nothing on offer the picker says so, rather than presenting an empty group.

Code

A gallery wired to a scope

A set of invocations to choose from, with the pick handed straight to a theme scope.

<!-- A gallery of invocations to choose from -->
<xtyle-theme-picker
	label="Theme"
	value="Quiet"
	themes='[
		{"name":"Default","algorithm":"xtyle-default"},
		{"name":"Quiet","algorithm":"xtyle-quiet"},
		{"name":"Grape","constraints":{"--accent":"#7c5cff"}},
		{"name":"Grape light","scheme":"light","constraints":{"--accent":"#7c5cff"}}
	]'
></xtyle-theme-picker>

<!-- Picking one hands the whole invocation to a scope -->
<script type="module">
	const picker = document.querySelector("xtyle-theme-picker");
	const scope = document.querySelector("xtyle-theme-scope");
	picker.addEventListener("xtyle:theme-pick", (event) => {
		const { algorithm, scheme, constraints } = event.detail.theme;
		if (algorithm) scope.setAttribute("algorithm", algorithm);
		if (scheme) scope.setAttribute("scheme", scheme);
		scope.setAttribute("constraints", JSON.stringify(constraints ?? {}));
	});
</script>