Skip to main content
astrocraft-ui/ components · 101

Checkbox Group

Free · MIT

Headless, 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

Install command
npx astrocraft-ui add forms/checkbox-group

Plain-CSS theme — no build step

Install command
npx astrocraft-ui add forms/checkbox-group --theme css

Tailwind theme — needs Tailwind v4

Install command
npx astrocraft-ui add forms/checkbox-group --theme tailwind

Plain-CSS theme, re-pointed onto Lumos tokens

Install command
npx astrocraft-ui add forms/checkbox-group --theme css --bridge lumos

Tailwind theme, re-pointed onto Lumos tokens

Install command
npx astrocraft-ui add forms/checkbox-group --theme tailwind --bridge lumos

Live 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.

Plan
Billing period
Notifications

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.

ComponentSlotsVariantsRuntime stateNative 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.

src/components/ui/forms/checkbox-group/CheckboxGroup.astro
---
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>
src/components/ui/forms/checkbox-group/CheckboxGroupItem.astro
---
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>
src/components/ui/forms/checkbox-group/CheckboxGroupLabel.astro
---
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>
src/components/ui/forms/checkbox-group/group-state.ts
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";
}
src/components/ui/forms/checkbox-group/index.ts
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.