Checkbox Group
Free · MITHeadless, accessible, and styled entirely through the attribute surface below — bring your own CSS system, or start from the reference theme.
Install
Without a flag you get no styling at all — plain HTML carrying the attribute surface below. --theme copies the same theme in two dialects;--bridge lumos re-points either one onto Lumos for Astro's tokens (it needs a theme to re-point, and Lumos itself, already in your project).
Unstyled — behavior only, you write the CSS
npx astrocraft-ui add forms/checkbox-groupPlain-CSS theme — no build step
npx astrocraft-ui add forms/checkbox-group --theme cssTailwind theme — needs Tailwind v4
npx astrocraft-ui add forms/checkbox-group --theme tailwindPlain-CSS theme, re-pointed onto Lumos tokens
npx astrocraft-ui add forms/checkbox-group --theme css --bridge lumosTailwind theme, re-pointed onto Lumos tokens
npx astrocraft-ui add forms/checkbox-group --theme tailwind --bridge lumosLive demo
RadioGroup & CheckboxGroup
Both are real fieldsets, so the legend names the group. The checkbox group’s first box is the select-all — tick one child and it goes indeterminate, the state HTML has no attribute for.
Attribute surface
With no class-merging machinery, these attributes are the whole extension point. State is a data-attribute or a native attribute; visibility is the hiddenattribute. Generated from source by pnpm slots.
| Component | Slots | Variants | Runtime state | Native state |
|---|---|---|---|---|
| `CheckboxGroup.astro` | `checkbox-group` | `data-orientation`: `vertical` · `horizontal` | — | — |
| `CheckboxGroupItem.astro` | `checkbox-group-item` `checkbox-group-item-label` | `data-toggle-all`: `true` when set | — | — |
| `CheckboxGroupLabel.astro` | `checkbox-group-label` | — | — | — |
Source
What the command copies — 5 files, alias-free so the copy lands standing alone. This is the whole component, shown without its comments; the files you install keep them, with the reasoning behind every rule.
---
import type { HTMLAttributes } from "astro/types";
type Props = HTMLAttributes<"fieldset"> & { orientation?: "vertical" | "horizontal" };
const { orientation = "vertical", class: className, ...rest } = Astro.props;
---
<fieldset class={className} data-slot="checkbox-group" data-orientation={orientation} {...rest}>
<slot />
</fieldset>
<script>
import { onReadyOnce } from "../../_once";
import { groupState } from "./group-state";
function wire(root: HTMLElement) {
const all = root.querySelector<HTMLInputElement>('[data-toggle-all="true"]');
const boxes = [...root.querySelectorAll<HTMLInputElement>('[data-slot="checkbox"]')].filter(
(box) => box !== all,
);
if (!all || boxes.length === 0) return;
const sync = () => {
const state = groupState(boxes.map((box) => box.checked));
all.checked = state === "all";
all.indeterminate = state === "some";
};
all.addEventListener("change", () => {
const next = all.checked;
for (const box of boxes) {
if (box.disabled || box.checked === next) continue;
box.checked = next;
box.dispatchEvent(new Event("change", { bubbles: true }));
}
sync();
});
for (const box of boxes) box.addEventListener("change", sync);
sync();
}
onReadyOnce('[data-slot="checkbox-group"]', wire);
</script>
---
import type { HTMLAttributes } from "astro/types";
import Checkbox from "../../forms/checkbox";
type Props = HTMLAttributes<"input"> & { toggleAll?: boolean };
const { toggleAll, class: className, ...rest } = Astro.props;
---
<label data-slot="checkbox-group-item">
<Checkbox class={className} data-toggle-all={toggleAll ? "true" : undefined} {...rest} />
<span data-slot="checkbox-group-item-label"><slot /></span>
</label>
---
import type { HTMLAttributes } from "astro/types";
type Props = HTMLAttributes<"legend">;
const { class: className, ...rest } = Astro.props;
---
<legend class={className} data-slot="checkbox-group-label" {...rest}><slot /></legend>
export type GroupState = "none" | "some" | "all";
export function groupState(flags: readonly boolean[]): GroupState {
if (flags.length === 0 || !flags.some(Boolean)) return "none";
return flags.every(Boolean) ? "all" : "some";
}
import CheckboxGroup from "./CheckboxGroup.astro";
import CheckboxGroupItem from "./CheckboxGroupItem.astro";
import CheckboxGroupLabel from "./CheckboxGroupLabel.astro";
export { type GroupState, groupState } from "./group-state";
export { CheckboxGroup, CheckboxGroupItem, CheckboxGroupLabel };
export default CheckboxGroup;
What you get
The component source, copied into your project by npx astrocraft-ui add forms/checkbox-group — no package dependency, no CSS to fight. Whatever the paid blocks compose, this is it.