Components · Data
Filter sheet
The Filters button of a phone layout: the active count on the button, checkbox groups in a side sheet, Clear all and Show results.
The UI Index has no filter on its 2,062 sites yet.
6 of 12 deploys
6 of 12 deploys
Both previews render the same markup; the HTML tab below is that markup without a framework.
Install
- React
- npx shadcn@
latest add https:// www.themes.dev/ caseroom/ r/ filter-sheet.json - Astro
- npx shadcn@
latest add https:// www.themes.dev/ caseroom/ r/ astro/ filter-sheet.json
Or copy the files below into your project. They need clsx, tailwind-merge, and the Caseroom theme. The HTML needs only the compiled CSS and the script.
// Filter sheet behaviour for Astro, HTML and React (React keeps no state of its own). Every checkbox change in a
// [data-filter-sheet] updates the trigger's count (hidden at 0) and Clear all's disabled state, and dispatches a
// bubbling `filter-change` ({ active: { [group]: string[] }, total }). Clear all unchecks every enabled box and keeps
// focus in the sheet. Opening, the focus trap, Escape and the return of focus are the Drawer's (native dialog).
declare global {
interface Window {
__caseroomFilterSheet?: boolean;
}
}
const update = (sheet: HTMLElement) => {
const boxes = [...sheet.querySelectorAll<HTMLInputElement>('input[type=checkbox]')];
const active: Record<string, string[]> = {};
for (const b of boxes) if (b.checked) (active[b.name] ??= []).push(b.value);
const total = boxes.filter((b) => b.checked).length;
const count = sheet.querySelector<HTMLElement>('[data-filter-sheet-count]');
if (count) {
count.hidden = total === 0;
const n = count.querySelector<HTMLElement>('[data-filter-sheet-total]');
if (n) n.textContent = String(total);
}
const clear = sheet.querySelector<HTMLButtonElement>('[data-filter-sheet-clear]');
if (clear) clear.disabled = !boxes.some((b) => b.checked && !b.disabled);
sheet.dispatchEvent(new CustomEvent('filter-change', { bubbles: true, detail: { active, total } }));
};
if (typeof window !== 'undefined' && !window.__caseroomFilterSheet) {
window.__caseroomFilterSheet = true;
document.addEventListener('change', (e) => {
const t = e.target as Element;
const sheet = t.closest<HTMLElement>('[data-filter-sheet]');
if (sheet && t.matches('input[type=checkbox]')) update(sheet);
});
document.addEventListener('click', (e) => {
const clear = (e.target as Element).closest<HTMLButtonElement>('[data-filter-sheet-clear]');
const sheet = clear?.closest<HTMLElement>('[data-filter-sheet]');
if (!clear || !sheet) return;
e.preventDefault();
for (const b of sheet.querySelectorAll<HTMLInputElement>('input[type=checkbox]')) if (!b.disabled) b.checked = false;
update(sheet);
// The button just turned itself off: keep focus on the first filter, not on the dialog.
sheet.querySelector<HTMLInputElement>('input[type=checkbox]:not(:disabled)')?.focus();
});
}
export {};import { cn } from '@/lib/utils';
import { Button } from '../button/button';
import { Checkbox } from '../checkbox/checkbox';
import { Drawer } from '../drawer/drawer';
import './filter-sheet.client';
import { activeCount, FILTER_PATH, filterSheet as c, type FilterSheetGroup, type FilterSheetOption } from './filter-sheet.variants';
export type { FilterSheetGroup, FilterSheetOption };
export interface FilterSheetProps {
/** Unique on the page: the sheet's id. */
id: string;
groups: FilterSheetGroup[];
/** The button and the sheet's title. */
label?: string;
/** The closing action, e.g. "Show 24 results". */
apply?: string;
variant?: 'outline' | 'default' | 'secondary' | 'ghost';
className?: string;
}
/** A "Filters" button with the active count that opens a side sheet of checkbox groups, Clear all and Show results.
* Listen to `filter-change` on a parent ({ active, total }); the boxes are uncontrolled. */
export function FilterSheet({ id, groups, label = 'Filters', apply = 'Show results', variant = 'outline', className }: FilterSheetProps) {
const total = activeCount(groups);
const opens = { command: 'show-modal', commandfor: id } as Record<string, string>;
return (
<div data-filter-sheet="" className={cn(c.root, className)}>
<Button variant={variant} data-variant={variant} {...opens} data-filter-sheet-trigger="">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" aria-hidden="true" className={c.icon}><path d={FILTER_PATH} /></svg>
{label}
<span data-filter-sheet-count="" hidden={total === 0} className={c.count}><span className="sr-only">, </span><span data-filter-sheet-total="">{total}</span><span className="sr-only"> active</span></span>
</Button>
<Drawer
id={id}
title={label}
footer={
<>
<button type="button" data-filter-sheet-clear="" disabled={total === 0} className={c.clear}>Clear all</button>
<Button type="submit" value="apply">{apply}</Button>
</>
}
>
<div className={c.form}>
{groups.map((g) => (
<fieldset key={g.name} className={c.group}>
<legend className={c.legend}>{g.legend}</legend>
{g.options.map((o) => (
<label key={o.value} className={c.option}>
<Checkbox name={g.name} value={o.value} defaultChecked={o.checked} disabled={o.disabled} />
{o.label}
{o.count !== undefined && <span className={c.optionCount}>{o.count}</span>}
</label>
))}
</fieldset>
))}
</div>
</Drawer>
</div>
);
}/** A filter sheet: the "Filters" button a phone layout shows instead of a row of chips. It carries the number of
* active filters and opens a Drawer with the filters as checkbox groups (fieldset and legend), Clear all and Show
* results. It filters nothing itself: listen to `filter-change` ({ active: { [group]: string[] }, total }) from
* [data-filter-sheet] and filter your list; Show results only closes the sheet. */
export const filterSheet = {
root: 'inline-flex',
// The count is part of the button's name ("Filters, 2 active"); hidden when nothing is on.
count:
'inline-flex h-5 min-w-5 items-center justify-center rounded-full bg-primary px-1.5 text-[11px] font-semibold text-primary-foreground tabular-nums [[data-variant=default]_&]:bg-primary-foreground [[data-variant=default]_&]:text-primary',
icon: 'size-4',
form: 'grid gap-6',
group: 'm-0 grid min-w-0 gap-0.5 border-0 p-0',
legend: 'mb-2 p-0 text-sm font-semibold',
// A whole row is the target (40px), the checkbox and the label in one click.
option: '-mx-2 flex min-h-10 cursor-pointer items-center gap-3 rounded-md px-2 text-sm transition-colors hover:bg-accent has-[:disabled]:cursor-not-allowed has-[:disabled]:opacity-50 has-[:disabled]:hover:bg-transparent',
optionCount: 'ml-auto text-xs text-muted-foreground tabular-nums',
clear:
'mr-auto -mx-1.5 inline-flex h-9 cursor-pointer items-center rounded-md px-1.5 text-sm font-medium underline decoration-foreground/30 underline-offset-4 transition-colors hover:decoration-foreground active:bg-pressed active:decoration-2 disabled:pointer-events-none disabled:no-underline disabled:opacity-50',
};
export type FilterSheetOption = { value: string; label: string; count?: number; checked?: boolean; disabled?: boolean };
export type FilterSheetGroup = { name: string; legend: string; options: FilterSheetOption[] };
export const activeCount = (groups: FilterSheetGroup[]) => groups.reduce((n, g) => n + g.options.filter((o) => o.checked).length, 0);
export const FILTER_PATH = 'M2.5 4.5h7M12.5 4.5h1M2.5 11.5h1M6.5 11.5h7M11 3a1.5 1.5 0 1 1 0 3a1.5 1.5 0 0 1 0-3M5 10a1.5 1.5 0 1 1 0 3a1.5 1.5 0 0 1 0-3';---
import { cn } from '@/lib/utils';
import Button from '../button/button.astro';
import Checkbox from '../checkbox/checkbox.astro';
import Drawer from '../drawer/drawer.astro';
import { activeCount, FILTER_PATH, filterSheet as c, type FilterSheetGroup } from './filter-sheet.variants';
interface Props {
/** Unique on the page: the sheet's id. */
id: string;
groups: FilterSheetGroup[];
/** The button and the sheet's title. */
label?: string;
/** The closing action, e.g. "Show 24 results". Kept current by your code. */
apply?: string;
variant?: 'outline' | 'default' | 'secondary' | 'ghost';
class?: string;
}
const { id, groups, label = 'Filters', apply = 'Show results', variant = 'outline', class: className } = Astro.props;
const total = activeCount(groups);
---
<div data-filter-sheet class={cn(c.root, className)}>
<Button variant={variant} data-variant={variant} command="show-modal" commandfor={id} data-filter-sheet-trigger="">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" aria-hidden="true" class={c.icon}><path d={FILTER_PATH} /></svg>
{label}
<span data-filter-sheet-count hidden={total === 0} class={c.count}><span class="sr-only">, </span><span data-filter-sheet-total>{total}</span><span class="sr-only"> active</span></span>
</Button>
<Drawer id={id} title={label}>
<div class={c.form}>
{
groups.map((g) => (
<fieldset class={c.group}>
<legend class={c.legend}>{g.legend}</legend>
{g.options.map((o) => (
<label class={c.option}>
<Checkbox name={g.name} value={o.value} checked={o.checked} disabled={o.disabled} />
{o.label}
{o.count !== undefined && <span class={c.optionCount}>{o.count}</span>}
</label>
))}
</fieldset>
))
}
</div>
<Fragment slot="footer">
<button type="button" data-filter-sheet-clear disabled={total === 0} class={c.clear}>Clear all</button>
<Button type="submit" value="apply">{apply}</Button>
</Fragment>
</Drawer>
</div>
<script>
import './filter-sheet.client';
</script>// Filter sheet behaviour for Astro, HTML and React (React keeps no state of its own). Every checkbox change in a
// [data-filter-sheet] updates the trigger's count (hidden at 0) and Clear all's disabled state, and dispatches a
// bubbling `filter-change` ({ active: { [group]: string[] }, total }). Clear all unchecks every enabled box and keeps
// focus in the sheet. Opening, the focus trap, Escape and the return of focus are the Drawer's (native dialog).
declare global {
interface Window {
__caseroomFilterSheet?: boolean;
}
}
const update = (sheet: HTMLElement) => {
const boxes = [...sheet.querySelectorAll<HTMLInputElement>('input[type=checkbox]')];
const active: Record<string, string[]> = {};
for (const b of boxes) if (b.checked) (active[b.name] ??= []).push(b.value);
const total = boxes.filter((b) => b.checked).length;
const count = sheet.querySelector<HTMLElement>('[data-filter-sheet-count]');
if (count) {
count.hidden = total === 0;
const n = count.querySelector<HTMLElement>('[data-filter-sheet-total]');
if (n) n.textContent = String(total);
}
const clear = sheet.querySelector<HTMLButtonElement>('[data-filter-sheet-clear]');
if (clear) clear.disabled = !boxes.some((b) => b.checked && !b.disabled);
sheet.dispatchEvent(new CustomEvent('filter-change', { bubbles: true, detail: { active, total } }));
};
if (typeof window !== 'undefined' && !window.__caseroomFilterSheet) {
window.__caseroomFilterSheet = true;
document.addEventListener('change', (e) => {
const t = e.target as Element;
const sheet = t.closest<HTMLElement>('[data-filter-sheet]');
if (sheet && t.matches('input[type=checkbox]')) update(sheet);
});
document.addEventListener('click', (e) => {
const clear = (e.target as Element).closest<HTMLButtonElement>('[data-filter-sheet-clear]');
const sheet = clear?.closest<HTMLElement>('[data-filter-sheet]');
if (!clear || !sheet) return;
e.preventDefault();
for (const b of sheet.querySelectorAll<HTMLInputElement>('input[type=checkbox]')) if (!b.disabled) b.checked = false;
update(sheet);
// The button just turned itself off: keep focus on the first filter, not on the dialog.
sheet.querySelector<HTMLInputElement>('input[type=checkbox]:not(:disabled)')?.focus();
});
}
export {};/** A filter sheet: the "Filters" button a phone layout shows instead of a row of chips. It carries the number of
* active filters and opens a Drawer with the filters as checkbox groups (fieldset and legend), Clear all and Show
* results. It filters nothing itself: listen to `filter-change` ({ active: { [group]: string[] }, total }) from
* [data-filter-sheet] and filter your list; Show results only closes the sheet. */
export const filterSheet = {
root: 'inline-flex',
// The count is part of the button's name ("Filters, 2 active"); hidden when nothing is on.
count:
'inline-flex h-5 min-w-5 items-center justify-center rounded-full bg-primary px-1.5 text-[11px] font-semibold text-primary-foreground tabular-nums [[data-variant=default]_&]:bg-primary-foreground [[data-variant=default]_&]:text-primary',
icon: 'size-4',
form: 'grid gap-6',
group: 'm-0 grid min-w-0 gap-0.5 border-0 p-0',
legend: 'mb-2 p-0 text-sm font-semibold',
// A whole row is the target (40px), the checkbox and the label in one click.
option: '-mx-2 flex min-h-10 cursor-pointer items-center gap-3 rounded-md px-2 text-sm transition-colors hover:bg-accent has-[:disabled]:cursor-not-allowed has-[:disabled]:opacity-50 has-[:disabled]:hover:bg-transparent',
optionCount: 'ml-auto text-xs text-muted-foreground tabular-nums',
clear:
'mr-auto -mx-1.5 inline-flex h-9 cursor-pointer items-center rounded-md px-1.5 text-sm font-medium underline decoration-foreground/30 underline-offset-4 transition-colors hover:decoration-foreground active:bg-pressed active:decoration-2 disabled:pointer-events-none disabled:no-underline disabled:opacity-50',
};
export type FilterSheetOption = { value: string; label: string; count?: number; checked?: boolean; disabled?: boolean };
export type FilterSheetGroup = { name: string; legend: string; options: FilterSheetOption[] };
export const activeCount = (groups: FilterSheetGroup[]) => groups.reduce((n, g) => n + g.options.filter((o) => o.checked).length, 0);
export const FILTER_PATH = 'M2.5 4.5h7M12.5 4.5h1M2.5 11.5h1M6.5 11.5h7M11 3a1.5 1.5 0 1 1 0 3a1.5 1.5 0 0 1 0-3M5 10a1.5 1.5 0 1 1 0 3a1.5 1.5 0 0 1 0-3';<div class="flex flex-wrap items-center justify-center gap-3">
<div data-filter-sheet class="inline-flex">
<button type="button" class="inline-flex shrink-0 cursor-pointer items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-[color,background-color,border-color,transform] select-none active:scale-[0.97] aria-busy:cursor-progress disabled:pointer-events-none disabled:opacity-50 aria-busy:disabled:opacity-80 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 border border-input bg-background hover:bg-accent hover:text-accent-foreground active:bg-pressed h-9 px-4" data-variant="outline" command="show-modal" commandfor="ex-filter-sheet" data-filter-sheet-trigger>
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" aria-hidden="true" class="size-4"><path d="M2.5 4.5h7M12.5 4.5h1M2.5 11.5h1M6.5 11.5h7M11 3a1.5 1.5 0 1 1 0 3a1.5 1.5 0 0 1 0-3M5 10a1.5 1.5 0 1 1 0 3a1.5 1.5 0 0 1 0-3"></path></svg>
Filters
<span data-filter-sheet-count class="inline-flex h-5 min-w-5 items-center justify-center rounded-full bg-primary px-1.5 text-[11px] font-semibold text-primary-foreground tabular-nums [[data-variant=default]_&]:bg-primary-foreground [[data-variant=default]_&]:text-primary">
<span class="sr-only">, </span>
<span data-filter-sheet-total>2</span>
<span class="sr-only"> active</span>
</span>
</button>
<dialog id="ex-filter-sheet" data-dialog data-side="right" aria-labelledby="ex-filter-sheet-title" class="fixed inset-y-0 m-0 h-dvh max-h-none w-[min(24rem,calc(100%-2.5rem))] max-w-none border-0 bg-popover p-0 text-popover-foreground shadow-2xl dark:shadow-[0_24px_64px_rgb(0_0_0/0.7)] backdrop:bg-black/50 backdrop:backdrop-blur-[2px] transition-[translate,opacity,display,overlay] transition-discrete duration-200 ease-out motion-reduce:translate-x-0 motion-reduce:opacity-0 motion-reduce:open:opacity-100 motion-reduce:starting:open:opacity-0 right-0 left-auto translate-x-full border-l open:translate-x-0 starting:open:translate-x-full">
<div class="flex h-full flex-col">
<div class="flex items-start justify-between gap-4 px-5 pt-5 pb-4 border-b">
<div class="min-w-0">
<h2 id="ex-filter-sheet-title" class="text-base font-semibold tracking-[-0.01em]">Filters</h2>
</div>
<form method="dialog" class="-mt-1.5 -mr-2 shrink-0">
<button type="submit" aria-label="Close" class="inline-flex size-8 cursor-pointer items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-accent hover:text-foreground active:bg-pressed">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" aria-hidden="true" class="size-4"><path d="M4 4l8 8M12 4l-8 8"></path></svg>
</button>
</form>
</div>
<div data-dialog-body class="flex-1 overflow-y-auto px-5 py-4 text-sm">
<div class="grid gap-6">
<fieldset class="m-0 grid min-w-0 gap-0.5 border-0 p-0">
<legend class="mb-2 p-0 text-sm font-semibold">Status</legend>
<label class="-mx-2 flex min-h-10 cursor-pointer items-center gap-3 rounded-md px-2 text-sm transition-colors hover:bg-accent has-[:disabled]:cursor-not-allowed has-[:disabled]:opacity-50 has-[:disabled]:hover:bg-transparent">
<span class="relative inline-flex size-4 shrink-0">
<input type="checkbox" class="peer size-4 shrink-0 cursor-pointer appearance-none rounded-[4px] border border-input bg-background transition-[color,background-color,border-color,transform] enabled:not-checked:hover:border-foreground enabled:not-checked:hover:bg-accent enabled:active:scale-90 enabled:not-checked:active:bg-pressed checked:border-primary checked:enabled:hover:border-[color-mix(in_oklab,var(--primary)_var(--hover-amount),var(--background))] checked:enabled:hover:bg-[color-mix(in_oklab,var(--primary)_var(--hover-amount),var(--background))] checked:enabled:active:border-[color-mix(in_oklab,var(--primary)_var(--press-amount),var(--press-mix))] checked:enabled:active:bg-[color-mix(in_oklab,var(--primary)_var(--press-amount),var(--press-mix))] checked:bg-primary indeterminate:border-primary indeterminate:bg-primary disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive" name="status" value="live" checked>
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="pointer-events-none absolute inset-0 hidden size-4 text-primary-foreground peer-checked:block"><path d="M3.5 8.5l3 3 6-7"></path></svg>
</span>
Live
<span class="ml-auto text-xs text-muted-foreground tabular-nums">8</span>
</label>
<label class="-mx-2 flex min-h-10 cursor-pointer items-center gap-3 rounded-md px-2 text-sm transition-colors hover:bg-accent has-[:disabled]:cursor-not-allowed has-[:disabled]:opacity-50 has-[:disabled]:hover:bg-transparent">
<span class="relative inline-flex size-4 shrink-0">
<input type="checkbox" class="peer size-4 shrink-0 cursor-pointer appearance-none rounded-[4px] border border-input bg-background transition-[color,background-color,border-color,transform] enabled:not-checked:hover:border-foreground enabled:not-checked:hover:bg-accent enabled:active:scale-90 enabled:not-checked:active:bg-pressed checked:border-primary checked:enabled:hover:border-[color-mix(in_oklab,var(--primary)_var(--hover-amount),var(--background))] checked:enabled:hover:bg-[color-mix(in_oklab,var(--primary)_var(--hover-amount),var(--background))] checked:enabled:active:border-[color-mix(in_oklab,var(--primary)_var(--press-amount),var(--press-mix))] checked:enabled:active:bg-[color-mix(in_oklab,var(--primary)_var(--press-amount),var(--press-mix))] checked:bg-primary indeterminate:border-primary indeterminate:bg-primary disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive" name="status" value="preview">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="pointer-events-none absolute inset-0 hidden size-4 text-primary-foreground peer-checked:block"><path d="M3.5 8.5l3 3 6-7"></path></svg>
</span>
Preview
<span class="ml-auto text-xs text-muted-foreground tabular-nums">3</span>
</label>
<label class="-mx-2 flex min-h-10 cursor-pointer items-center gap-3 rounded-md px-2 text-sm transition-colors hover:bg-accent has-[:disabled]:cursor-not-allowed has-[:disabled]:opacity-50 has-[:disabled]:hover:bg-transparent">
<span class="relative inline-flex size-4 shrink-0">
<input type="checkbox" class="peer size-4 shrink-0 cursor-pointer appearance-none rounded-[4px] border border-input bg-background transition-[color,background-color,border-color,transform] enabled:not-checked:hover:border-foreground enabled:not-checked:hover:bg-accent enabled:active:scale-90 enabled:not-checked:active:bg-pressed checked:border-primary checked:enabled:hover:border-[color-mix(in_oklab,var(--primary)_var(--hover-amount),var(--background))] checked:enabled:hover:bg-[color-mix(in_oklab,var(--primary)_var(--hover-amount),var(--background))] checked:enabled:active:border-[color-mix(in_oklab,var(--primary)_var(--press-amount),var(--press-mix))] checked:enabled:active:bg-[color-mix(in_oklab,var(--primary)_var(--press-amount),var(--press-mix))] checked:bg-primary indeterminate:border-primary indeterminate:bg-primary disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive" name="status" value="failed">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="pointer-events-none absolute inset-0 hidden size-4 text-primary-foreground peer-checked:block"><path d="M3.5 8.5l3 3 6-7"></path></svg>
</span>
Failed
<span class="ml-auto text-xs text-muted-foreground tabular-nums">1</span>
</label>
</fieldset>
<fieldset class="m-0 grid min-w-0 gap-0.5 border-0 p-0">
<legend class="mb-2 p-0 text-sm font-semibold">Framework</legend>
<label class="-mx-2 flex min-h-10 cursor-pointer items-center gap-3 rounded-md px-2 text-sm transition-colors hover:bg-accent has-[:disabled]:cursor-not-allowed has-[:disabled]:opacity-50 has-[:disabled]:hover:bg-transparent">
<span class="relative inline-flex size-4 shrink-0">
<input type="checkbox" class="peer size-4 shrink-0 cursor-pointer appearance-none rounded-[4px] border border-input bg-background transition-[color,background-color,border-color,transform] enabled:not-checked:hover:border-foreground enabled:not-checked:hover:bg-accent enabled:active:scale-90 enabled:not-checked:active:bg-pressed checked:border-primary checked:enabled:hover:border-[color-mix(in_oklab,var(--primary)_var(--hover-amount),var(--background))] checked:enabled:hover:bg-[color-mix(in_oklab,var(--primary)_var(--hover-amount),var(--background))] checked:enabled:active:border-[color-mix(in_oklab,var(--primary)_var(--press-amount),var(--press-mix))] checked:enabled:active:bg-[color-mix(in_oklab,var(--primary)_var(--press-amount),var(--press-mix))] checked:bg-primary indeterminate:border-primary indeterminate:bg-primary disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive" name="framework" value="astro" checked>
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="pointer-events-none absolute inset-0 hidden size-4 text-primary-foreground peer-checked:block"><path d="M3.5 8.5l3 3 6-7"></path></svg>
</span>
Astro
<span class="ml-auto text-xs text-muted-foreground tabular-nums">6</span>
</label>
<label class="-mx-2 flex min-h-10 cursor-pointer items-center gap-3 rounded-md px-2 text-sm transition-colors hover:bg-accent has-[:disabled]:cursor-not-allowed has-[:disabled]:opacity-50 has-[:disabled]:hover:bg-transparent">
<span class="relative inline-flex size-4 shrink-0">
<input type="checkbox" class="peer size-4 shrink-0 cursor-pointer appearance-none rounded-[4px] border border-input bg-background transition-[color,background-color,border-color,transform] enabled:not-checked:hover:border-foreground enabled:not-checked:hover:bg-accent enabled:active:scale-90 enabled:not-checked:active:bg-pressed checked:border-primary checked:enabled:hover:border-[color-mix(in_oklab,var(--primary)_var(--hover-amount),var(--background))] checked:enabled:hover:bg-[color-mix(in_oklab,var(--primary)_var(--hover-amount),var(--background))] checked:enabled:active:border-[color-mix(in_oklab,var(--primary)_var(--press-amount),var(--press-mix))] checked:enabled:active:bg-[color-mix(in_oklab,var(--primary)_var(--press-amount),var(--press-mix))] checked:bg-primary indeterminate:border-primary indeterminate:bg-primary disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive" name="framework" value="next">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="pointer-events-none absolute inset-0 hidden size-4 text-primary-foreground peer-checked:block"><path d="M3.5 8.5l3 3 6-7"></path></svg>
</span>
Next.js
<span class="ml-auto text-xs text-muted-foreground tabular-nums">4</span>
</label>
<label class="-mx-2 flex min-h-10 cursor-pointer items-center gap-3 rounded-md px-2 text-sm transition-colors hover:bg-accent has-[:disabled]:cursor-not-allowed has-[:disabled]:opacity-50 has-[:disabled]:hover:bg-transparent">
<span class="relative inline-flex size-4 shrink-0">
<input type="checkbox" class="peer size-4 shrink-0 cursor-pointer appearance-none rounded-[4px] border border-input bg-background transition-[color,background-color,border-color,transform] enabled:not-checked:hover:border-foreground enabled:not-checked:hover:bg-accent enabled:active:scale-90 enabled:not-checked:active:bg-pressed checked:border-primary checked:enabled:hover:border-[color-mix(in_oklab,var(--primary)_var(--hover-amount),var(--background))] checked:enabled:hover:bg-[color-mix(in_oklab,var(--primary)_var(--hover-amount),var(--background))] checked:enabled:active:border-[color-mix(in_oklab,var(--primary)_var(--press-amount),var(--press-mix))] checked:enabled:active:bg-[color-mix(in_oklab,var(--primary)_var(--press-amount),var(--press-mix))] checked:bg-primary indeterminate:border-primary indeterminate:bg-primary disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive" name="framework" value="remix" disabled>
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="pointer-events-none absolute inset-0 hidden size-4 text-primary-foreground peer-checked:block"><path d="M3.5 8.5l3 3 6-7"></path></svg>
</span>
Remix
<span class="ml-auto text-xs text-muted-foreground tabular-nums">0</span>
</label>
</fieldset>
<fieldset class="m-0 grid min-w-0 gap-0.5 border-0 p-0">
<legend class="mb-2 p-0 text-sm font-semibold">Region</legend>
<label class="-mx-2 flex min-h-10 cursor-pointer items-center gap-3 rounded-md px-2 text-sm transition-colors hover:bg-accent has-[:disabled]:cursor-not-allowed has-[:disabled]:opacity-50 has-[:disabled]:hover:bg-transparent">
<span class="relative inline-flex size-4 shrink-0">
<input type="checkbox" class="peer size-4 shrink-0 cursor-pointer appearance-none rounded-[4px] border border-input bg-background transition-[color,background-color,border-color,transform] enabled:not-checked:hover:border-foreground enabled:not-checked:hover:bg-accent enabled:active:scale-90 enabled:not-checked:active:bg-pressed checked:border-primary checked:enabled:hover:border-[color-mix(in_oklab,var(--primary)_var(--hover-amount),var(--background))] checked:enabled:hover:bg-[color-mix(in_oklab,var(--primary)_var(--hover-amount),var(--background))] checked:enabled:active:border-[color-mix(in_oklab,var(--primary)_var(--press-amount),var(--press-mix))] checked:enabled:active:bg-[color-mix(in_oklab,var(--primary)_var(--press-amount),var(--press-mix))] checked:bg-primary indeterminate:border-primary indeterminate:bg-primary disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive" name="region" value="eu">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="pointer-events-none absolute inset-0 hidden size-4 text-primary-foreground peer-checked:block"><path d="M3.5 8.5l3 3 6-7"></path></svg>
</span>
Europe
<span class="ml-auto text-xs text-muted-foreground tabular-nums">9</span>
</label>
<label class="-mx-2 flex min-h-10 cursor-pointer items-center gap-3 rounded-md px-2 text-sm transition-colors hover:bg-accent has-[:disabled]:cursor-not-allowed has-[:disabled]:opacity-50 has-[:disabled]:hover:bg-transparent">
<span class="relative inline-flex size-4 shrink-0">
<input type="checkbox" class="peer size-4 shrink-0 cursor-pointer appearance-none rounded-[4px] border border-input bg-background transition-[color,background-color,border-color,transform] enabled:not-checked:hover:border-foreground enabled:not-checked:hover:bg-accent enabled:active:scale-90 enabled:not-checked:active:bg-pressed checked:border-primary checked:enabled:hover:border-[color-mix(in_oklab,var(--primary)_var(--hover-amount),var(--background))] checked:enabled:hover:bg-[color-mix(in_oklab,var(--primary)_var(--hover-amount),var(--background))] checked:enabled:active:border-[color-mix(in_oklab,var(--primary)_var(--press-amount),var(--press-mix))] checked:enabled:active:bg-[color-mix(in_oklab,var(--primary)_var(--press-amount),var(--press-mix))] checked:bg-primary indeterminate:border-primary indeterminate:bg-primary disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive" name="region" value="us">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="pointer-events-none absolute inset-0 hidden size-4 text-primary-foreground peer-checked:block"><path d="M3.5 8.5l3 3 6-7"></path></svg>
</span>
United States
<span class="ml-auto text-xs text-muted-foreground tabular-nums">3</span>
</label>
</fieldset>
</div>
</div>
<form method="dialog" data-dialog-footer class="flex flex-wrap items-center justify-end gap-2 border-t bg-muted/50 px-5 py-3">
<button type="button" data-filter-sheet-clear class="mr-auto -mx-1.5 inline-flex h-9 cursor-pointer items-center rounded-md px-1.5 text-sm font-medium underline decoration-foreground/30 underline-offset-4 transition-colors hover:decoration-foreground active:bg-pressed active:decoration-2 disabled:pointer-events-none disabled:no-underline disabled:opacity-50">
Clear all
</button>
<button type="submit" class="inline-flex shrink-0 cursor-pointer items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-[color,background-color,border-color,transform] select-none active:scale-[0.97] aria-busy:cursor-progress disabled:pointer-events-none disabled:opacity-50 aria-busy:disabled:opacity-80 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 bg-primary text-primary-foreground hover:bg-[color-mix(in_oklab,var(--primary)_var(--hover-amount),var(--background))] active:bg-[color-mix(in_oklab,var(--primary)_var(--press-amount),var(--press-mix))] h-9 px-4" value="apply">
Show 6 deploys
</button>
</form>
</div>
</dialog>
</div>
<p class="text-sm text-muted-foreground tabular-nums">6 of 12 deploys</p>
</div><script type="module" src="/caseroom/r/html/filter-sheet.js"></script>