| name | component-composition |
| description | How to combine fundamental-styles components correctly - required wrappers, nesting rules, and common composition patterns |
| metadata | {"tags":["composition","structure","nesting","wrappers","patterns"],"keywords":["composition","combine-components","nesting","wrapper-elements","form-item","input-group","popover","dialog-structure","required-wrappers","component-hierarchy","parent-child","structure"]} |
Component Composition
This skill explains how to combine fundamental-styles components correctly, including required wrappers, nesting rules, and common composition patterns.
When to Use This Skill
Use this skill when:
- The user asks "How do I combine [component A] with [component B]?"
- The user needs to understand component structure and nesting
- The user asks about required wrapper elements
- The user's component isn't working due to incorrect composition
- The user asks "Why do I need this wrapper element?"
- The user wants to understand parent-child relationships between components
Core Composition Principles
1. Always Include Required Wrappers
Many components require specific wrapper elements to function correctly:
<label class="fd-form-label">Name</label>
<input class="fd-input" />
<div class="fd-form-item">
<label class="fd-form-label">Name</label>
<input class="fd-input" />
</div>
2. Follow the Component Hierarchy
Each component has a specific structure - don't skip levels:
<table class="fd-table">
<tbody class="fd-table__body">
<td class="fd-table__cell">Data</td>
</tbody>
</table>
<table class="fd-table">
<tbody class="fd-table__body">
<tr class="fd-table__row">
<td class="fd-table__cell">Data</td>
</tr>
</tbody>
</table>
3. Understand Block Independence
Each component (block) is independent. Nesting blocks doesn't create a hierarchy:
<button class="fd-button">
<i class="sap-icon--edit"></i>
</button>
.fd-button and .sap-icon--edit are two separate blocks living together, not a parent-child BEM relationship.
Common Composition Patterns
Form Item Composition
Components: Form Item + Form Label + Input + Form Message
Structure:
<div class="fd-form-item">
<label class="fd-form-label" for="input-id">Label Text</label>
<input class="fd-input" id="input-id" type="text" />
</div>
With validation message:
<div class="fd-form-item">
<label class="fd-form-label fd-form-label--required" for="email">Email</label>
<input class="fd-input is-error" id="email" type="email" aria-describedby="email-error" />
<span class="fd-form-message fd-form-message--error" id="email-error"> Invalid email format </span>
</div>
Why these wrappers?
.fd-form-item provides spacing and layout structure
- Keeps label, input, and message grouped together
- Enables proper responsive behavior
Input Group Composition
Components: Input Group + Input + Button
Basic input group:
<div class="fd-input-group">
<input class="fd-input fd-input-group__input" type="text" placeholder="Search" />
<span class="fd-input-group__addon fd-input-group__addon--button">
<button class="fd-button fd-button--transparent" aria-label="Search">
<i class="sap-icon--search"></i>
</button>
</span>
</div>
With text addon:
<div class="fd-input-group">
<span class="fd-input-group__addon fd-input-group__addon--before">$</span>
<input class="fd-input fd-input-group__input" type="number" />
<span class="fd-input-group__addon fd-input-group__addon--after">.00</span>
</div>
Why these wrappers?
.fd-input-group establishes flex layout
.fd-input-group__input modifier allows input to flex-grow
.fd-input-group__addon positions addons correctly
- Ensures seamless visual connection between elements
Popover + Menu (Contextual Menu)
Components: Popover (container) + Button (control/trigger) + Menu (body content)
Structure:
<div class="fd-popover">
<div class="fd-popover__control">
<button class="fd-button fd-button--transparent" aria-label="More actions">
<i class="sap-icon--overflow"></i>
</button>
</div>
<div class="fd-popover__body" aria-hidden="true">
<nav class="fd-menu">
<ul class="fd-menu__list">
<li class="fd-menu__item">
<a class="fd-menu__link" href="#">
<span class="fd-menu__title">Edit</span>
</a>
</>
Delete
Why this composition?
.fd-popover manages positioning and visibility
.fd-popover__control wraps the trigger element
.fd-popover__body contains the content (menu, form, etc.)
- Separates trigger from content for proper overlay behavior
Dialog Composition
Components: Dialog + Bar (header/footer) + Title + Button
Structure:
<div class="fd-dialog fd-dialog--active">
<div class="fd-dialog__content">
<header class="fd-dialog__header fd-bar fd-bar--header">
<div class="fd-bar__left">
<div class="fd-bar__element">
<h3 class="fd-title fd-title--h5">Dialog Title</h3>
</div>
</div>
<div class="fd-bar__right">
<div class="fd-bar__element">
<button class="fd-button fd-button--transparent" aria-label="Close">
<i class="sap-icon--decline"></i>
</button>
</div>
</div>
Dialog content goes here
Save
Cancel
Why this structure?
.fd-dialog is the overlay container
.fd-dialog__content contains all dialog content
.fd-dialog__header uses .fd-bar for consistent header layout
.fd-dialog__footer uses .fd-bar for consistent footer layout
.fd-bar__left / .fd-bar__right position elements
.fd-bar__element wraps each item in the bar
Button + Icon + Text
Components: Button + Icon + Button Text
Icon-only button (no text):
<button class="fd-button fd-button--transparent" aria-label="Edit">
<i class="sap-icon--edit"></i>
</button>
Button with icon and text:
<button class="fd-button fd-button--emphasized">
<i class="sap-icon--save" role="presentation" aria-hidden="true"></i>
<span class="fd-button__text">Save</span>
</button>
Why the difference?
- Icon-only: needs
aria-label for accessibility (no visible text)
- Icon + text: text provides label, icon is decorative (
aria-hidden="true")
.fd-button__text wrapper allows proper spacing between icon and text
Table + Toolbar + Pagination
Components: Toolbar + Table + Pagination
Structure:
<div class="fd-toolbar">
<div class="fd-toolbar__group">
<button class="fd-button fd-button--transparent fd-button--compact">
<i class="sap-icon--add" role="presentation" aria-hidden="true"></i>
<span class="fd-button__text">Add</span>
</button>
</div>
</div>
<table class="fd-table">
<thead class="fd-table__header">
<tr class="fd-table__row">
<th class="fd-table__cell">Name</th>
<th class="fd-table__cell">Status</th>
John Doe
Active
1
2
3
Why separate components?
- Each is an independent block
- Toolbar provides actions for the table
- Table displays data
- Pagination controls data navigation
- Keeping them separate allows flexible positioning
Checkbox/Radio + Label
Components: Checkbox/Radio + Label
Checkbox structure:
<input type="checkbox" class="fd-checkbox" id="checkbox-1" />
<label class="fd-checkbox__label" for="checkbox-1">
<span class="fd-checkbox__checkmark" aria-hidden="true"></span>
<span class="fd-checkbox__label-container">
<span class="fd-checkbox__text">Accept terms and conditions</span>
</span>
</label>
Radio structure:
<input type="radio" class="fd-radio" id="radio-1" name="group" />
<label class="fd-radio__label" for="radio-1">
<span class="fd-radio__text">Option 1</span>
</label>
Why this structure?
- Input comes first (required for
:checked CSS selectors)
- Label wraps visual elements
.fd-checkbox__checkmark is the custom styled checkbox
.fd-checkbox__label-container and .fd-checkbox__text allow proper text wrapping
Panel + Content
Components: Panel + Panel Header + Panel Body
Structure:
<div class="fd-panel">
<div class="fd-panel__header">
<h3 class="fd-panel__title">Panel Title</h3>
<button class="fd-button fd-button--transparent fd-panel__button" aria-label="Expand">
<i class="sap-icon--slim-arrow-down"></i>
</button>
</div>
<div class="fd-panel__content">
<p>Panel content goes here</p>
</div>
</div>
Expandable panel:
<div class="fd-panel fd-panel--expandable">
<div class="fd-panel__header">
<h3 class="fd-panel__title">Expandable Panel</h3>
<button class="fd-button fd-button--transparent fd-panel__button" aria-label="Expand" aria-expanded="false">
<i class="sap-icon--slim-arrow-right"></i>
</button>
</div>
<div class="fd-panel__content" hidden>
<p>Collapsible content</p>
</div>
</div>
Why this structure?
.fd-panel is the container
.fd-panel__header contains title and expand button
.fd-panel__content holds the body content
--expandable modifier enables collapse/expand behavior
List + List Items
Components: List + List Item + List Title + List Text
Basic list:
<ul class="fd-list">
<li class="fd-list__item">
<span class="fd-list__title">Item 1</span>
</li>
<li class="fd-list__item">
<span class="fd-list__title">Item 2</span>
</li>
</ul>
List with byline (additional text):
<ul class="fd-list fd-list--byline">
<li class="fd-list__item">
<div class="fd-list__content">
<div class="fd-list__title">John Doe</div>
<div class="fd-list__byline">Software Engineer</div>
</div>
</li>
<li class="fd-list__item">
<div class="fd-list__content">
<div class="fd-list__title">Jane Smith</div>
<div class="fd-list__byline">Product Manager</div>
</div>
</li>
</ul>
Why these elements?
.fd-list establishes the list container
.fd-list__item wraps each list entry
.fd-list__content groups title and byline
.fd-list__title is the main text
.fd-list__byline is secondary/supporting text
Card Structure
Components: Card + Card Header + Card Content
Structure:
<div class="fd-card">
<div class="fd-card__header">
<h3 class="fd-card__title">Card Title</h3>
<div class="fd-card__subtitle">Subtitle text</div>
</div>
<div class="fd-card__content">
<p>Card content goes here</p>
</div>
<div class="fd-card__footer">
<button class="fd-button fd-button--transparent">Action</button>
</div>
</div>
Why this structure?
.fd-card is the container with shadow/border
.fd-card__header groups title and subtitle
.fd-card__content holds the main content
.fd-card__footer (optional) contains actions
Breadcrumb Composition
Components: Breadcrumb + Breadcrumb Item + Link
Structure:
<nav aria-label="Breadcrumb">
<ul class="fd-breadcrumb">
<li class="fd-breadcrumb__item">
<a class="fd-breadcrumb__link" href="#">Home</a>
</li>
<li class="fd-breadcrumb__item">
<a class="fd-breadcrumb__link" href="#">Products</a>
</li>
<li class="fd-breadcrumb__item">
<a class="fd-breadcrumb__link" aria-current="page">Details</a>
</li>
</ul>
</nav>
With custom separator:
<ul class="fd-breadcrumb fd-breadcrumb--backslash">
<li class="fd-breadcrumb__item">
<a class="fd-breadcrumb__link" href="#">Home</a>
</li>
<li class="fd-breadcrumb__item">
<a class="fd-breadcrumb__link" href="#">Products</a>
</li>
<li class="fd-breadcrumb__item">
<span class="fd-breadcrumb__link" aria-current="page">Details</span>
</li>
</ul>
Why this structure?
<nav> with aria-label="Breadcrumb" for accessibility
.fd-breadcrumb is the list container
.fd-breadcrumb__item wraps each crumb
.fd-breadcrumb__link for clickable items
- Current page uses
aria-current="page"
- Separator modifiers:
--backslash, --double-greater-than
Message Strip Composition
Components: Message Strip + Message Strip Text
Basic message:
<div class="fd-message-strip fd-message-strip--error" role="alert">
<p class="fd-message-strip__text">Error: Unable to save changes</p>
</div>
With dismiss button:
<div class="fd-message-strip fd-message-strip--warning" role="alert">
<p class="fd-message-strip__text">Warning: Changes are not saved</p>
<button class="fd-button fd-button--transparent fd-message-strip__close" aria-label="Close">
<i class="sap-icon--decline"></i>
</button>
</div>
Why this structure?
.fd-message-strip is the container with semantic color
role="alert" announces to screen readers
.fd-message-strip__text contains the message
.fd-message-strip__close (optional) allows dismissal
Required vs Optional Wrappers
Always Required
These wrappers are mandatory for the component to work:
| Component | Required Wrapper | Why |
|---|
| Form fields | .fd-form-item | Layout and spacing |
| Input group | .fd-input-group | Flex layout for addons |
| Popover | .fd-popover__control + .fd-popover__body | Positioning logic |
| Dialog | .fd-dialog__content | Centering and sizing |
| Table cells | .fd-table__row | Table structure |
| List items | .fd-list__item | List styling |
| Menu items | .fd-menu__item | Menu structure |
Optional Wrappers
These enhance functionality but aren't strictly required:
| Component | Optional Wrapper | Benefit |
|---|
| Panel | .fd-panel__header | Structured header area |
| Card | .fd-card__header | Organized title area |
| Button | .fd-button__text | Icon-text spacing |
| Checkbox | .fd-checkbox__label-container | Text wrapping |
Common Composition Mistakes
❌ Missing Base Class with Modifier
<button class="fd-button--emphasized">Save</button>
<button class="fd-button fd-button--emphasized">Save</button>
❌ Skipping Required Wrapper
<label class="fd-form-label">Name</label>
<input class="fd-input" />
<div class="fd-form-item">
<label class="fd-form-label">Name</label>
<input class="fd-input" />
</div>
❌ Wrong Element Order
<label class="fd-checkbox__label" for="cb1">
<span class="fd-checkbox__checkmark"></span>
<span class="fd-checkbox__text">Option</span>
</label>
<input type="checkbox" class="fd-checkbox" id="cb1" />
<input type="checkbox" class="fd-checkbox" id="cb1" />
<label class="fd-checkbox__label" for="cb1">
<span class="fd-checkbox__checkmark"></span>
<span class="fd-checkbox__text">Option</span>
</label>
❌ Missing Icon Accessibility
<button class="fd-button">
<i class="sap-icon--edit"></i>
</button>
<button class="fd-button" aria-label="Edit">
<i class="sap-icon--edit"></i>
</button>
❌ Incorrect Hierarchy
<table class="fd-table">
<tbody class="fd-table__body">
<td class="fd-table__cell">Data</td>
</tbody>
</table>
<table class="fd-table">
<tbody class="fd-table__body">
<tr class="fd-table__row">
<td class="fd-table__cell">Data</td>
</tr>
</tbody>
</table>
Composition Decision Tree
"Do I need a wrapper?"
- Check the component documentation - Does it show a wrapper?
- Look for double underscores -
.fd-component__element means it must be inside .fd-component
- Test without it - If layout breaks, you need it
- Common wrappers:
- Form fields →
.fd-form-item
- Input with addons →
.fd-input-group
- Popover content →
.fd-popover__body
- Dialog content →
.fd-dialog__content
- Bar items →
.fd-bar__element
"Which component goes inside which?"
Container → Content relationship:
fd-form-item
└─ fd-form-label
└─ fd-input
└─ fd-form-message
fd-input-group
└─ fd-input-group__addon
└─ fd-input-group__input (with fd-input)
└─ fd-input-group__addon
fd-popover
└─ fd-popover__control
└─ fd-button (trigger)
└─ fd-popover__body
└─ fd-menu (or any content)
fd-dialog
└─ fd-dialog__content
└─ fd-dialog__header (fd-bar)
└─ fd-dialog__body
└─ fd-dialog__footer (fd-bar)
fd-card
└─ fd-card__header
└─ fd-card__content
└─ fd-card__footer
fd-panel
└─ fd-panel__header
└─ fd-panel__content
fd-table
└─ fd-table__header
└─ fd-table__row
└─ fd-table__cell
└─ fd-table__body
└─ fd-table__row
└─ fd-table__cell
Quick Reference: Common Compositions
| Pattern | Structure |
|---|
| Form Field | fd-form-item > fd-form-label + fd-input + fd-form-message |
| Input Group | fd-input-group > fd-input-group__addon + fd-input + fd-input-group__addon |
| Button + Icon | fd-button > sap-icon + fd-button__text |
| Contextual Menu | fd-popover > fd-popover__control (button) + fd-popover__body (menu) |
| Dialog | fd-dialog > fd-dialog__content > header + body + footer |
| Card | fd-card > fd-card__header + fd-card__content + fd-card__footer |
| Panel | fd-panel > fd-panel__header + fd-panel__content |
| Table | fd-table > fd-table__header/body > fd-table__row > fd-table__cell |
| List | fd-list > fd-list__item > fd-list__content > fd-list__title |
| Checkbox | input.fd-checkbox + label.fd-checkbox__label > checkmark + text |
Summary
Key Principles:
- Always include required wrappers (form-item, input-group, popover__body, etc.)
- Follow the documented component hierarchy (don't skip levels)
- Icon-only buttons need
aria-label
- Icon + text buttons need
fd-button__text wrapper
- Always include base class before modifiers
- Check component documentation for correct structure
When in doubt:
- Look for
__ (double underscore) - indicates element must be inside its block
- Check if layout/spacing looks wrong - you probably need a wrapper
- Reference the component guidance or layout patterns skills for examples