Real interfaces are built from many components working together. This lesson covers the common patterns for composing Lit components: property/event data flow and slot-based structural composition.
Two Ways to Compose Components
There are two complementary composition patterns in Lit: property/event composition, where a parent passes data down via properties and listens for events dispatched back up; and slot-based composition, where a parent's markup structure is projected into a child component's template, letting the child control layout while the parent controls content.
Most real components combine both — a <data-table> might accept a .rows=${data} property for its data while also exposing named slots for custom column headers or action buttons.
TodoApp owns the actual data and passes it down via a property; todo-list never mutates todos directly, only dispatches an event describing what happened.
A 'compound component' is a parent element (<tab-group>) that coordinates a set of related child elements (<tab-panel>) slotted inside it.
The parent typically discovers its children via slotchange and assignedElements(), or via a shared context/controller (covered in the controllers lessons).
Children can expose simple properties (like label) that the parent reads to build its own UI (like generated tab buttons).
This pattern mirrors how native <select>/<option> and <table>/<tr>/<td> work in HTML itself.
Composition Patterns Cheat Sheet
Choosing a composition approach for a given relationship.
A reactive controller or shared context (see Controllers lessons)
Avoiding Excessive Prop Drilling
Passing data down through many layers of intermediate components that don't themselves use it (only forwarding it further down) is called 'prop drilling', and it makes components harder to reuse independently. For deeply nested sharing needs, a reactive controller with a shared, injected state object (or the Context community protocol) is usually a better fit than threading properties through every intermediate layer.
Building a Simple Compound Component
A tab group is a classic compound component: the parent renders tab buttons based on its slotted <tab-panel> children's label properties, and controls which panel is visible.
The parent reads each slotted <tab-panel>'s label property directly and toggles its hidden attribute to control visibility.
Common Mistakes
Threading a property through several intermediate components that don't use it themselves, instead of using a shared controller/context.
Building a compound component that reaches into a child's Shadow DOM internals directly instead of using its public property/attribute API.
Forgetting to re-run slot discovery logic (assignedElements()) when slotted children are added or removed later.
Duplicating the same data-fetching or state logic across sibling components instead of sharing it through a common ancestor or controller.
Key Takeaways
Property/event composition (data down, events up) and slot-based composition are the two core patterns.
Most real components combine both, using slots for structure and properties/events for data.
Compound components coordinate slotted children by reading their public properties, similar to native <select>/<option>.
For deeply nested sharing needs, prefer a shared controller or context over prop drilling through many layers.
Pro Tip
Before building a compound component from scratch, check whether the community's Context protocol (@lit/context) already solves your specific sharing problem — it's designed exactly for passing data to arbitrarily nested descendants without manual slot-discovery plumbing.
You've completed the slots and composition series. Next, learn how to style Lit components with scoped CSS.