The Compound Component Pattern (And When to Use Angular Aria Instead) In short: A family of components that share state through dependency injection, so consumers arrange the parts and the components coordinate themselves. But check first — if the pattern is on its list, that work is already done and done better. Content projection gave us slots. This lesson is about what happens when the parts of a component need to talk to each other — an accordion where only one panel opens at a time, a tab set, a segmented control. Check first: is this already solved? Before building anything, look at the list from Chapter 3: > Autocomplete, Listbox, Select, Multiselect, Combobox, Menu, Menubar, Toolbar, Accordion , Tabs , Tree, Grid. Accordion is on it. So is Tabs. If you are building either, stop and use the framework: gives the one-at-a-time behaviour. You also get arrow-key navigation between triggers, and , correct and wiring, and on an so panel content is only created when the panel opens. Every class here — , — is yours. The directives are headless. So why learn the pattern at all? Because covers twelve patterns and your design system will need more than twelve components. When you build the thirteenth, this is how. Building one: the parts Our example is a segmented filter for the dispatch dashboard — a row of mutually exclusive options, not on the Aria list. Three pieces that must cooperate: a group that owns which option is selected, and options that need to know whether they are the selected one. The parent owns the state The children find the parent through DI Angular's element tree is also the injector tree, so a child can inject its parent component directly. The child derives its own appearance from the parent's signal and dispatches back up by calling a method. No inputs threaded through, no outputs wired by the consumer. What the consumer writes Compare with the version where the consumer manages the state: The feature team should be managing dispatch data, not the internal mechanics of a segmented control. Making the family explicit Two refinements turn a set of components into a supported API. Fail loudly when used wrong. An option outside a group gets a , which is accurate but unhelpful: Export the family together , so consumers import one thing: Slot directives, revisited Lesson two introduced and as directives rather than bare attribute selectors. That is the same pattern at its simplest — the directive exists purely to give the compiler and the editor something typed to work with. Two things to keep straight, because they look redundant: The directive gives you autocomplete, compile-time typo checking, and something to query with . The attribute on still needs a CSS selector, because projection is a compiler-level DOM operation and knows nothing about classes. So you write the selector in both places, and they are doing different jobs: "But I thought injecting the parent component created tight coupling" This objection is well-trained — parent injection is a real code smell in most contexts, and someone who flags it is usually right. The general rule is: a component should not know about its parent, because that makes it usable in exactly one place. injecting is bad, and it is bad for an obvious reason — the avatar can now only live on that page. Compound components are the case where that reasoning does not apply, and the distinction is worth naming precisely. is not a general-purpose component that happens to be used inside a group. It is a part of the group. It has no meaning outside one — "an option with nothing to be an option of" is not a thing. The coupling is not accidental; it is the definition. Two tests that separate the good case from the bad one: 1. Does the child make sense alone? An avatar does. A segmented option does not. Coupling to a parent is only a smell when the child could have stood on its own. 2. Are they shipped and versioned together? and are in the same folder, released in the same package, exported in the same array. They are one component with several elements. And look at what the alternative costs. Without parent injection, the state has to travel through the consumer's template: Parent injection Props threaded by the consumer --- --- --- Bindings per option, in the consumer 0 2 ( , ) Consumer must know "only one at a time" No Yes — they implement it Consumer can get it wrong No Yes — two selected at once Adding a keyboard model later Library change only Every consumer changes Lines in a 5-option consumer template 5 15 Who owns the invariant The library Every consumer, separately That last row is the real argument. "Only one option is selected" is an invariant of the component. If the consumer implements it, then twenty consumers implement it, and some of them implement it wrong. Angular's own APIs work this way, which is a reasonable sanity check: 's finds its group, finds its . Same mechanism. The rule, stated carefully: inject a parent when the child is a part of…