GitHub

/ Foundations — 02

Composition over configuration.

Kumiki components are compound primitives. The Root sets up the state, named subcomponents present it. The child snippet pattern lets you swap any element without losing behaviour.

Compound shape

Every Layer 4 component follows the same outline:

<Combobox.Root>
  <Combobox.Input />
  <Combobox.Listbox>
    {#each items as item}
      <Combobox.Item value={item}>{item.label}</Combobox.Item>
    {/each}
  </Combobox.Listbox>
</Combobox.Root>

The child snippet

Need to render an <a> instead of a <button>? Use the child snippet. The component hands you the props it would have spread; you decide what tag they go on. This replaces the Radix/Bits v1 asChild pattern.

<Toggle.Root>
  {#snippet child({ props })}
    <a href={resolve('/destination')} {...props}>Navigate</a>
  {/snippet}
</Toggle.Root>

The component still controls ARIA — only the rendered tag changes. There is no asChild prop in Kumiki and there never will be.

Layered escape hatches

If child isn't enough — say you need to coordinate three custom DOM nodes — drop down a layer. Use the headless attachment factories at @kumiki/headless and write your own JSX/HTML around them.

<script lang="ts">
  import { createToggle } from '@kumiki/headless/toggle';
  const t = createToggle({ pressed: $state(false) });
</script>

<button {@attach t.root} class="my-fancy-toggle">
  {t.pressed ? 'On' : 'Off'}
</button>

Generics propagate

Components that take a typed value (Combobox, RadioGroup, Select, FormField) propagate that generic from the Root. Inner subcomponents read it via getContext. No need to repeat the type at every level.