sap/fundamental-styles

component-composition

How to combine fundamental-styles components correctly - required wrappers, nesting rules, and common composition patterns

First seen May 14, 2026

Installation

$ npx skills add sap/fundamental-styles --skill component-composition

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from sap/fundamental-styles · top by installs.

npx skills add sap/fundamental-styles

Browse all from sap/fundamental-styles

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Repository health

Stars 232
License LICENSES
Default branch main
Open issues 11
Status Active

Skill metadata

Parsed from SKILL.md frontmatter.

More metadata
tags
["composition","structure","nesting","wrappers","patterns"]
keywords
[]

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 22,625 B
  • docs SUMMARY.md 151 B

History

  1. First seen on skills.sh
  2. First recorded snapshot · 22 installs

SKILL.md

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:

<!-- ❌ Wrong - missing wrapper -->
<label class="fd-form-label">Name</label>
<input class="fd-input" />

<!-- ✅ Correct - wrapped in form-item -->
<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:

<!-- ❌ Wrong - missing table__row -->
<table class="fd-table">
    <tbody class="fd-table__body">
        <td class="fd-table__cell">Data</td>
    </tbody>
</table>

<!-- ✅ Correct - proper hierarchy -->
<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:

<!-- Both are independent blocks, not parent-child -->
<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">
    <!-- Control/Trigger -->
    <div class="fd-popover__control">
        <button class="fd-button fd-button--transparent" aria-label="More actions">
            <i class="sap-icon--overflow"></i>
        </button>
    </div>

    <!-- Body with Menu -->
    <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>
                </li>
                <li class="fd-menu__item">
                    <a class="fd-menu__link" href="#">
                        <span class="fd-menu__title">Delete</span>
                    </a>
                </li>
            </ul>
        </nav>
    </div>
</div>

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 -->
        <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>
        </header>

        <!-- Body -->
        <div class="fd-dialog__body">
            <p>Dialog content goes here</p>
        </div>

        <!-- Footer -->
        <footer class="fd-dialog__footer fd-bar fd-bar--footer">
            <div class="fd-bar__right">
                <div class="fd-bar__element">
                    <button class="fd-button fd-button--emphasized">Save</button>
                </div>
                <div class="fd-bar__element">
                    <button class="fd-button fd-button--transparent">Cancel</button>
                </div>
            </div>
        </footer>
    </div>
</div>

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-barleft / .fd-barright 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:

<!-- Toolbar above table -->
<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 -->
<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>
        </tr>
    </thead>
    <tbody class="fd-table__body">
        <tr class="fd-table__row">
            <td class="fd-table__cell">John Doe</td>
            <td class="fd-table__cell">Active</td>
        </tr>
    </tbody>
</table>

<!-- Pagination below table -->
<div class="fd-pagination">
    <nav class="fd-pagination__nav">
        <a href="#" class="fd-button fd-button--transparent" aria-label="Previous">
            <i class="sap-icon--navigation-left-arrow"></i>
        </a>
        <a href="#" class="fd-button fd-button--transparent">1</a>
        <a href="#" class="fd-button fd-button--transparent is-active" aria-current="page">2</a>
        <a href="#" class="fd-button fd-button--transparent">3</a>
        <a href="#" class="fd-button fd-button--transparent" aria-label="Next">
            <i class="sap-icon--navigation-right-arrow"></i>
        </a>
    </nav>
</div>

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-checkboxlabel-container and .fd-checkboxtext 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-popovercontrol + .fd-popoverbody 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

<!-- Wrong -->
<button class="fd-button--emphasized">Save</button>

<!-- Correct -->
<button class="fd-button fd-button--emphasized">Save</button>

❌ Skipping Required Wrapper

<!-- Wrong - missing form-item wrapper -->
<label class="fd-form-label">Name</label>
<input class="fd-input" />

<!-- Correct -->
<div class="fd-form-item">
    <label class="fd-form-label">Name</label>
    <input class="fd-input" />
</div>

❌ Wrong Element Order

<!-- Wrong - label before input breaks :checked selector -->
<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" />

<!-- Correct - input first -->
<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

<!-- Wrong - icon-only button without label -->
<button class="fd-button">
    <i class="sap-icon--edit"></i>
</button>

<!-- Correct - with aria-label -->
<button class="fd-button" aria-label="Edit">
    <i class="sap-icon--edit"></i>
</button>

❌ Incorrect Hierarchy

<!-- Wrong - missing table__row -->
<table class="fd-table">
    <tbody class="fd-table__body">
        <td class="fd-table__cell">Data</td>
    </tbody>
</table>

<!-- Correct -->
<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?"

  1. Check the component documentation - Does it show a wrapper?
  2. Look for double underscores - .fd-component__element means it must be inside .fd-component
  3. Test without it - If layout breaks, you need it
  4. Common wrappers:

- Form fields → .fd-form-item - Input with addons → .fd-input-group - Popover content → .fd-popoverbody - Dialog content → .fd-dialogcontent - 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-groupaddon + fd-input + fd-input-groupaddon
Button + Icon fd-button > sap-icon + fd-button__text
Contextual Menu fd-popover > fd-popovercontrol (button) + fd-popoverbody (menu)
Dialog fd-dialog > fd-dialog__content > header + body + footer
Card fd-card > fd-cardheader + fd-cardcontent + fd-card__footer
Panel fd-panel > fd-panelheader + fd-panelcontent
Table fd-table > fd-tableheader/body > fd-tablerow > fd-table__cell
List fd-list > fd-listitem > fd-listcontent > fd-list__title
Checkbox input.fd-checkbox + label.fd-checkbox__label > checkmark + text

Summary

Key Principles:

  1. Always include required wrappers (form-item, input-group, popover\\body, etc.)
  2. Follow the documented component hierarchy (don't skip levels)
  3. Icon-only buttons need aria-label
  4. Icon + text buttons need fd-button__text wrapper
  5. Always include base class before modifiers
  6. 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