Slots & Composition
How Vue/React slots and children map to Kiso's yield and sub-part composition.
Default slot
Vue’s <slot>, React’s {children}, and Kiso’s yield all do the same
thing — render whatever the caller puts inside the component:
Vue
<Card>
Hello
</Card>
React
<Card>
Hello
</Card>
Kiso
<%%= kui(:card) do %>
Hello
<%% end %>
Named slots → sub-part composition
Vue has named slots (<template #header>). React uses compound components
(<Card.Header>). Kiso uses sub-part partials:
Vue
<Card>
<template #header>
<CardTitle>Title</CardTitle>
</template>
Content here
<template #footer>
<Button>Save</Button>
</template>
</Card>
React (shadcn)
<Card>
<CardHeader>
<CardTitle>Title</CardTitle>
</CardHeader>
<CardContent>
Content here
</CardContent>
<CardFooter>
<Button>Save</Button>
</CardFooter>
</Card>
Kiso
<%%= kui(:card) do %>
<%%= kui(:card, :header) do %>
<%%= kui(:card, :title) { "Title" } %>
<%% end %>
<%%= kui(:card, :content) do %>
Content here
<%% end %>
<%%= kui(:card, :footer) do %>
<%%= kui(:button) { "Save" } %>
<%% end %>
<%% end %>
kui(:card, :header) renders the card/_header.html.erb partial. Each
sub-part is its own file with its own locals — just like how shadcn
decomposes Card into CardHeader, CardTitle, CardContent, and
CardFooter.
Why composition instead of content_for?
Rails does have content_for / yield :name, which looks like named slots:
<% # This works but has a gotcha %>
<%= kui(:card) do %>
<% content_for :header, "Title" %>
Body content
<% end %>
The problem: content_for is page-global. If you render two Cards on the
same page, their :header content bleeds into each other. Sub-part
composition doesn’t have this problem — each kui(:card, :header) call is
isolated.
This is the same reason shadcn and Nuxt UI use compound components instead of Vue’s named slots for complex layouts.
Choosing between yield and sub-parts
Use yield (default slot) for simple components where the caller provides
a single block of content:
<%= kui(:badge, color: :success) { "Active" } %>
<%= kui(:button, variant: :solid) do %>
Save changes
<% end %>
Use sub-parts for structured components with multiple content areas:
<%= kui(:alert, color: :info) do %>
<%= kui(:alert, :title) { "Heads up!" } %>
<%= kui(:alert, :description) { "This is important." } %>
<% end %>
Each component’s docs page lists its available sub-parts.