Skip to content

Instantly share code, notes, and snippets.

@tabatkins
Last active August 26, 2022 17:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tabatkins/bda9adac50ba7d5616e60ecce9e5cb30 to your computer and use it in GitHub Desktop.
Save tabatkins/bda9adac50ba7d5616e60ecce9e5cb30 to your computer and use it in GitHub Desktop.
Various ARIA component patterns, and how they'd be naively built using toggle

Checkbox

<div class=checkbox></div>

<style>
.checkbox {
	toggle: checked;
}
.checkbox:toggle(checked)::before {
	content: "✔";
}
</style>

Checkbox Group

<div class=checkbox></div>
<div class=checkbox></div>
<div class=checkbox></div>
<div class=checkbox></div>
<div class=checkbox></div>

<style>
.checkbox {
	toggle: checked;
}
.checkbox:toggle(checked)::before {
	content: "✔";
}
</style>

Radio Group

<div class=radio-group>
	<div class=radio></div>
	<div class=radio></div>
	<div class=radio></div>
	<div class=radio></div>
	<div class=radio></div>
</div>

<style>
.radio-group {
	toggle-group: radio;
}
.radio {
	toggle: radio group;
}
.radio:toggle(radio 1)::before {
	content: "•";
}
.radio:toggle(radio 0)::before {
	content: "◦";
}
</style>

Disclosure

<div class=details>
	<div class=summary>...</div>
	<div class=contents>...</div>
</div>

<style>
.details {
	toggle-root: disclosure;
}
.summary {
	toggle-trigger: disclosure;
}
.summary:toggle(disclosure 0)::before {
	content: "▶";
}
.summary:toggle(disclosure 1)::before {
	content: "▼";
}
.contents {
	toggle-visibility: toggle disclosure;
}
</style>

Tab Panel

<div class=tab-group>
	<div class=tab>one</div>
	<div class=tab-panel>one content</div>
	<div class=tab>two</div>
	<div class=tab-panel>two content</div>
</div>

<style>
.tab-group {
	toggle-group: tab;
}
.tab {
	toggle: tab group;
}
.tab:toggle(tab) {
	font-weight: bold;
}
.tab-panel {
	toggle-visibility: tab;
}

.tab-group {
	display: grid;
	grid-template-rows: min-content auto;
	grid-template-columns: auto auto; /* match the number of tabs */
}
.tab {
	grid-row: 1;
}
.tab-panel {
	grid-row: 2;
	grid-column: 1 / -1;
}
</style>

At the moment, this is the only markup pattern that works with the design, and comes with some limitations. It will be improved somewhat with some planned CSS updates: one that lets you flow multiple elements into the same grid cell (so you can make a single-column grid, rather than predicting the number of tabs), and another that uses grid order for the a11y/focus/etc trees (rather than markup order).

I'm also considering an upgrade to Toggle that lets you set up a single toggle with N states, automatically associate a set of toggle-trigger elements with a distinct state, and then also associate another set of elements with each of the states, so this example would use a single toggle (rather than one per tab, all in a group) and could be written in the "all tabs first, followed by all panels" pattern that is sometimes preferred. The CSS might then look like:

.tab-group {
	toggle: tab auto-number;
}
.tab {
	toggle-auto-number: tab trigger;
	toggle-trigger: tab set auto-number;
}
.tab-panel {
	toggle-auto-number: tab use;
	toggle-visiblity: toggle tab auto-number;
}

Here, toggle-auto-number will associate the element + toggle name with an auto-incrementing integer, which can then be used by the toggle properties. The second keyword is a freeform tag used to set up multiple auto-numberings for the same toggle name, so here the .tab and .tab-panel elements don't interfere with each other; the Nth tab and the Nth tab-panel get the same integer, and thus associate with each other, regardless of whether the markup is written tabs-first or tabs-interspersed.

Accordion

<div class=accordion-group>
	<div class=accordion-header>one</div>
	<div class=accordion-panel>one content</div>
	<div class=accordion-header>two</div>
	<div class=accordion-panel>two content</div>
</div>

<style>
.accordion-group {
	toggle-group: accordion;
}
.accordion-header {
	toggle: accordion group;
}
.accordion-header:toggle(accordion 0)::before {
	content: "▶";
}
.accordion-header:toggle(accordion 1)::before {
	content: "▼";
}
.accordion-panel {
	toggle-visibility: accordion;
}
</style>

This implements a "one open at a time" accordion. If you want the opening states to be independent, just remove the toggle-group from .accordion-group and the group keyword from .accordion-header.

Tree View

<ul>
	<li class=tree-node>
		<button class=tree-toggle><span class=to-open>[+]</span><span class=to-close>[-]</span></button>
		<div class=tree-content>...</div>
	<li class=tree-node>
		<button class=tree-toggle><span class=to-open>[+]</span><span class=to-close>[-]</span></button>
		<div class=tree-content>
			<ul>
				<li class=tree-node>
					<button class=tree-toggle><span class=to-open>[+]</span><span class=to-close>[-]</span></button>
					<div class=tree-content>...</div>
				<li class=tree-node>
					<button class=tree-toggle><span class=to-open>[+]</span><span class=to-close>[-]</span></button>
					<div class=tree-content>...</div>
			</ul>
		</div>
</ul>

<style>
.tree-node {
	toggle-root: tree;
}
.tree-toggle {
	toggle-trigger: tree;
}
.tree-toggle:toggle(tree 0) > .to-close,
.tree-toggle:toggle(tree 1) > .to-open {
	display: none;
}
.tree-content {
	toggle-visiblity: toggle tree;
}
</style>

Carousel

n/a, this is only tangentially related to Toggle. (Might be useful to hook up a toggle to scroll-snap state, so it can be used for styling purposes.)

But talk to Nicole Sullivan about this; we're planning work on this exact pattern right now.

Cycling Button

<button class=color-change>
	<span class=red>red</span>
	<span class=green>green</span>
	<span class=blue>blue</span>
</button>

<style>
.color-change {
	toggle: color [red green blue];
}
.red:not(:toggle(color red)),
.green:not(:toggle(color green)),
.blue:not(:toggle(color blue)) {
	display: none;
}
</style>

Again, somewhat annoying since you have to manually annotate the sub-elements with each state. Like the Tabs example, this will be improved by letting a set of elements automatically associate themselves with unique states from a toggle, so it might look something like:

.color-change {
	toggle: color auto-number;
}
.color-change > span {
	toggle-auto-number: color;
	toggle-visibility: toggle color auto-number;
	/* depends on toggle-visiblity being able to link to a specific toggle value
	   rather than just "is any active state",
	   which is planned but not yet in the spec */
}

Hamburger Menu

Identical to Disclosure.

Popup

Can potentially be identical to Disclosure, but more likely should be JS-driven to use the Popup API.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment