Lesson 5 — Named slots: the problem and the solution
A panel with a single yield point is clean and simple. But what about a Card component with a distinct header, body, and footer — each needing independent rich content?
The naive attempt — and why it fails
The instinct is to define methods for each slot and call them from the template:
|
|
Used like this:
|
|
Run it and the output is:
|
|
Empty elements. The slots are not populated.
Why it fails
The problem is timing. When the component is invoked, view_template runs immediately. At that point @header, @body, and @footer are all nil — the three empty elements are rendered. Then the block executes, populating the instance variables — but the HTML has already been built and returned. The slot content arrives too late.
The fix is to yield the block before rendering any HTML, so the slot methods run and populate their instance variables first.
vanish — yield first, render second
vanish yields the block and discards any HTML output it produces:
|
|
Now the sequence is correct:
vanishyields the block —card.header,card.body,card.footerrun- Each slot method calls
capture(&)— storing rendered HTML in an instance variable vanishreturns — all three instance variables are populated- The
articlerenders —raw safe(@header)etc. output the captured HTML
capture and raw safe
capture runs a block and returns its Phlex output as a string rather than writing it to the buffer. This lets us store rendered HTML for later use.
raw safe outputs a pre-rendered HTML string back to the buffer, bypassing escaping. This is safe here because the content came from capture — it was already escaped by Phlex when each element inside the block was rendered.
vanish vs yield(self) if block_given?
Both yield the block with the component as the argument. The difference:
| Yields self | Discards HTML output | |
|---|---|---|
yield(self) if block_given? |
Yes | No |
vanish(&) |
Yes | Yes |
For named slot components, vanish is always the right choice. The slot methods use capture internally, so their output goes into instance variables rather than the buffer. Any HTML that accidentally reaches the buffer during the block execution would appear in the wrong place — vanish prevents this.
Use yield only when you genuinely want the block’s HTML output to appear at that point in the template — the simple single yield case from Lesson 4.
The full Card component
Pico styles <article>, <header>, and <footer> natively — the structure is genuinely semantic HTML with no extra classes needed:
|
|
Adding Card to the demo
|
|
Block syntax note: When calling Kit methods like
Card(...)with a simple block, use{}braces. When calling.calldirectly with a multi-line block, usedo...endor assign to a variable first. See the block precedence note in Lesson 6 for a full explanation.
Exercise — Components::Section
Create app/components/section.rb. Build a Components::Section component with a title: prop and two named slots — body and aside.
Expected usage:
|
|
Expected output:
|
|
Then add it to the demo:
|
|
Solution
|
|