Skip to content

Checklist — Guidelines

When to use

PatternUse
catnip-checklistLabelled multi-select from an options array (Figma checkbox Examples).
catnip-checkbox aloneSingle option, consent, or a row you place yourself outside a checklist.
catnip-dropdown multipleMany options in a compact panel; checkbox chrome is internal.

Binding

vue
<catnip-checklist
  label="Choose multiple"
  required
  intent="danger"
  supporting-text="Resolve the option-level errors below."
  .options="options"
  .modelValue="selected"
  @update:modelValue="selected = $event.detail[0] ?? $event.detail"
/>
js
const selected = ref(["a", "b"]);

const options = ref([
  { value: "a", label: "Option A" },
  {
    value: "b",
    label: "Option B",
    intent: "danger",
    supportingText: "Cannot be selected together with Option A."
  },
  { value: "c", label: "Option C" }
]);

Bind .options and .modelValue on the host (see Component API).

Validation (group + per option)

Use two levels:

LevelSet onExample
Groupcatnip-checklistintent, supporting-text“Select at least one option.”
RowEach options[] item → intent, supportingText“Cannot be selected with Option A.”

Per-row intent is only neutral or danger (same as catnip-checkbox). Group intent also supports warning and success for the field supporting-text block.

Static row errors

Put intent and supportingText directly on the option object (see live playground — Option B shows a row error while A and B are selected).

Dynamic row errors (your app updates options)

Recompute the options array when selection or server validation changes:

js
import { computed, ref } from "vue";

const selected = ref([]);

const baseOptions = [
  { value: "a", label: "Option A" },
  { value: "b", label: "Option B" },
  { value: "c", label: "Option C" }
];

const options = computed(() => {
  const hasA = selected.value.includes("a");
  const hasB = selected.value.includes("b");
  const conflict = hasA && hasB;

  return baseOptions.map((opt) => {
    if (opt.value !== "b") return opt;
    if (!conflict) return opt;
    return {
      ...opt,
      intent: "danger",
      supportingText: "Cannot be selected together with Option A."
    };
  });
});

const groupSupportingText = computed(() =>
  conflict ? "Resolve the option-level errors below." : ""
);

const groupIntent = computed(() => (conflict ? "danger" : "neutral"));
vue
<catnip-checklist
  label="Choose multiple"
  .intent="groupIntent"
  .supportingText="groupSupportingText"
  .options="options"
  .modelValue="selected"
  @update:modelValue="selected = $event.detail[0] ?? $event.detail"
/>

supportingText on an option accepts a string, string[], or { text, intent?, icon? } (same as Input).

Accessibility

  • The group uses role="group" with aria-labelledby pointing at the field label when visible.
  • Row intent="danger" sets aria-invalid on that checkbox’s native input.
  • Toggling updates modelValue on the checklist host.
  • Do not nest catnip-checkbox inside catnip-dropdown options — see Checkbox — Guidelines.

Catnip Design System by Signicat