Skip to main content
astrocraft-ui/ components · 101

Toggle Count

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/toggle-count

Plain-CSS theme — no build step

Install command
npx astrocraft-ui add forms/toggle-count --theme css

Tailwind theme — needs Tailwind v4

Install command
npx astrocraft-ui add forms/toggle-count --theme tailwind

Plain-CSS theme, re-pointed onto Lumos tokens

Install command
npx astrocraft-ui add forms/toggle-count --theme css --bridge lumos

Tailwind theme, re-pointed onto Lumos tokens

Install command
npx astrocraft-ui add forms/toggle-count --theme tailwind --bridge lumos

Live demo

Toggle Count

MonthlyAnnual

$19 per month

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
`ToggleCount.astro``toggle-count` `toggle-count-off` `toggle-count-on`—`data-checked`—
`ToggleCountValue.astro``toggle-count-value`———

Source

What the command copies — 3 files, alias-free so the copy lands standing alone. This is the whole component; there is nothing else.

src/components/ui/forms/toggle-count/ToggleCount.astro
---
// src/components/ui/forms/toggle-count/ToggleCount.astro — headless primitive (see ../../README.md).
// A two-value toggle (e.g. Monthly / Annual pricing) built on the Switch primitive.
// Every <ToggleCountValue for={id}> on the page swaps between its `min` (off) and `max` (on) text when
// this toggle flips — wired by the small bundled script. `id` links the toggle to its values. The two
// side labels are plain text (only the switch is interactive, so a label can't fight the toggle).
import type { HTMLAttributes } from "astro/types";

import Switch from "../../forms/switch";

type Props = HTMLAttributes<"div"> & { id: string; offLabel?: string; onLabel?: string };

const { id, offLabel = "Monthly", onLabel = "Annual", class: className, ...rest } = Astro.props;
---

<div
  class={className}
  data-slot="toggle-count"
  data-toggle-count-id={id}
  data-checked="false"
  {...rest}
>
  <span data-slot="toggle-count-off">{offLabel}</span>
  <Switch id={id} aria-label={`Toggle between ${offLabel} and ${onLabel}`} />
  <span data-slot="toggle-count-on">{onLabel}</span>
</div>

<script>
  import { onReadyOnce } from "../../_once";

  function wire(root: HTMLElement) {
    const input = root.querySelector<HTMLInputElement>('input[type="checkbox"]');
    const id = root.dataset.toggleCountId;
    if (!input || !id) return;
    const values = document.querySelectorAll<HTMLElement>(
      `[data-slot="toggle-count-value"][data-toggle-count="${id}"]`,
    );

    const sync = () => {
      const checked = input.checked;
      values.forEach((el) => {
        el.textContent = (checked ? el.dataset.max : el.dataset.min) ?? "";
      });
      root.dataset.checked = String(checked);
    };

    input.addEventListener("change", sync);
    sync();
  }

  onReadyOnce('[data-slot="toggle-count"]', wire);
</script>
src/components/ui/forms/toggle-count/ToggleCountValue.astro
---
// src/components/ui/forms/toggle-count/ToggleCountValue.astro — ToggleCount compound part (see ../../README.md).
// A value that swaps between `min` (toggle off) and `max` (toggle on). `for` must match the linked
// ToggleCount's `id`. Renders `min` by default so it's correct before hydration.
import type { HTMLAttributes } from "astro/types";

type Props = HTMLAttributes<"span"> & { for: string; min: string | number; max: string | number };

const { for: target, min, max, class: className, ...rest } = Astro.props;
---

<span
  data-slot="toggle-count-value"
  data-toggle-count={target}
  data-min={String(min)}
  data-max={String(max)}
  class={className}
  {...rest}>{String(min)}</span
>
src/components/ui/forms/toggle-count/index.ts
import ToggleCount from "./ToggleCount.astro";
import ToggleCountValue from "./ToggleCountValue.astro";

export { ToggleCount, ToggleCountValue };
export default ToggleCount;

What you get

The component source, copied into your project by npx astrocraft-ui add forms/toggle-count — no package dependency, no CSS to fight. Whatever the paid blocks compose, this is it.