Compound Components
Compound components allow you to organize complex components with multiple views into a directory structure, and dynamically switch between different templates based on component state.
Basic Compound Components
By default, components render the partial at app/views/live/component_name.html.live.erb. Mark a component as compound to organize templates in a directory:
module Live
class Checkout < LiveCable::Component
compound
reactive :step, -> { "cart" }
reactive :items, -> { [] }
actions :proceed_to_shipping
def proceed_to_shipping
self.step = "shipping"
end
end
endWith compound, the component looks for templates in app/views/live/checkout/. By default, it renders app/views/live/checkout/component.html.live.erb.
Dynamic Templates with template_state
Override the template_state method to dynamically switch between different templates:
module Live
class Wizard < LiveCable::Component
compound
reactive :current_step, -> { "account" }
reactive :form_data, -> { {} }
actions :next_step, :previous_step
def template_state
current_step # Renders app/views/live/wizard/{current_step}.html.live.erb
end
def next_step(params)
form_data.merge!(params)
self.current_step = case current_step
when "account" then "billing"
when "billing" then "confirmation"
else "complete"
end
end
def previous_step
self.current_step = case current_step
when "billing" then "account"
when "confirmation" then "billing"
else current_step
end
end
end
endThis creates a multi-step wizard with templates in:
app/views/live/wizard/account.html.live.erbapp/views/live/wizard/billing.html.live.erbapp/views/live/wizard/confirmation.html.live.erbapp/views/live/wizard/complete.html.live.erb
When templates switch, LiveCable's partial rendering system handles it efficiently by rendering all dynamic parts with the new template while reusing static parts when possible. See the Partial Rendering Guide for details.
Example: Checkout Flow
A 2-step checkout that collects shipping details and confirms the order.
Component Class
module Live
class Checkout < LiveCable::Component
compound
reactive :step, -> { "shipping" }
reactive :shipping, -> { {} }
reactive :errors, -> { {} }
actions :confirm, :place_order, :back
def template_state
step
end
def confirm(params)
if params[:name].blank? || params[:address].blank?
self.errors = { base: "Name and address are required" }
return
end
self.shipping = params.permit(:name, :address, :city, :postcode).to_h
self.errors = {}
self.step = "confirmation"
end
def place_order
Order.create!(shipping: shipping, user: current_user)
self.step = "complete"
end
def back
self.step = "shipping"
end
end
endTemplates
Shipping (app/views/live/checkout/shipping.html.live.erb):
<div class="checkout">
<h2>Shipping Details</h2>
<% if errors[:base] %>
<p class="error"><%= errors[:base] %></p>
<% end %>
<form live-form="confirm">
<div>
<label>Full Name</label>
<input type="text" name="name" value="<%= shipping[:name] %>">
</div>
<div>
<label>Address</label>
<input type="text" name="address" value="<%= shipping[:address] %>">
</div>
<div>
<label>City</label>
<input type="text" name="city" value="<%= shipping[:city] %>">
</div>
<div>
<label>Postcode</label>
<input type="text" name="postcode" value="<%= shipping[:postcode] %>">
</div>
<button type="submit">Review Order</button>
</form>
</div>Confirmation (app/views/live/checkout/confirmation.html.live.erb):
<div class="checkout">
<h2>Confirm Your Order</h2>
<div class="shipping-summary">
<p><strong>Shipping to:</strong></p>
<p><%= shipping[:name] %></p>
<p><%= shipping[:address] %>, <%= shipping[:city] %> <%= shipping[:postcode] %></p>
</div>
<div class="actions">
<button live-action="back">Back</button>
<button live-action="place_order">Place Order</button>
</div>
</div>Complete (app/views/live/checkout/complete.html.live.erb):
<div class="checkout">
<h2>Order Placed!</h2>
<p>Thanks <%= shipping[:name] %>, your order is on its way.</p>
<a href="/orders">View your orders</a>
</div>Use Cases
Compound components are ideal for:
- Multi-step forms: Wizards, onboarding flows, checkout processes
- State machines: Components with distinct states (loading, success, error, empty)
- Modal dialogs: Different content based on the modal's purpose
- Tabs: Switch between different content areas
- Dashboard widgets: Different views based on data availability
Best Practices
Do
✅ Use compound components for complex, multi-state components
✅ Keep template names descriptive and reflective of the state
✅ Use template_state to return a simple string or symbol
✅ Organize shared partials in the component's directory
✅ Keep state transitions clear and documented
Don't
❌ Don't use compound components for simple components
❌ Don't make template_state logic complex
❌ Don't forget to create all referenced templates
❌ Don't use compound when a single template with conditionals would suffice