UI Patterns 2 .story.yml Format
When to Use
Write
.story.ymlfiles when adding or documenting components for the UI Patterns 2 pattern library — any SDC theme withui_patterns_libraryenabled. No Node.js or Storybook.js required. Use Storybook.js tools when you need interactive Controls.
Write .story.yml files when:
- Adding new components to a UI Suite DaisyUI sub-theme or any UI Patterns 2 SDC theme
- Documenting example states of any SDC component for the pattern library
- Previewing components at /admin/appearance/ui/components
Decision
Complete Top-Level Schema
Source: ui_patterns/modules/ui_patterns_library/src/StoryPluginManager.php — $defaults array is the authoritative key list.
| Key | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Human-readable display name shown in the pattern library UI |
description |
string | No | Subtitle shown below the story name on the component page |
component |
string | No | Override target component as provider:component_id. Omit when story is in the same folder as the component |
props |
object | No | Key-value pairs matching the component's JSON schema props |
slots |
object | No | Named slot content — keys match slot IDs in component.yml. Values are slot value definitions |
library_wrapper |
string | No | Inline Twig template string. Use {{ _story }} to place the rendered component. Must be a string, not a YAML object |
File naming regex enforced by DirectoryWithMetadataDiscovery:
/^([a-z0-9_-])+\.([a-z0-9_-])+\.story\.yml$/i
Pattern: {component_id}.{story_id}.story.yml — both segments are [a-z0-9_-]+.
Discovery scans components/ directories recursively across all enabled modules and themes. The story ID derived from the filename becomes the plugin's machineName.
Props
Props are direct scalar values (string, boolean, integer, object) matching the component's JSON schema definition. The StoriesSyntaxConverter does not process props — they are passed through as-is.
props:
variant: primary # string enum
heading_level: 2 # integer
soft: true # boolean
attributes:
class: ["bg-base-100", "w-96", "shadow-sm"] # Attribute object
icon:
pack_id: hero_outline_24 # icon prop type (object, not a slot render array)
icon_id: information-circle
settings:
stroke_width: 2
stroke_color: stroke-info
Key distinction: icon as a prop is a structured object with pack_id/icon_id/settings. Different from type: icon as a slot value.
Slot Value Types — Complete Reference
Slot values are Drupal render arrays. The StoriesSyntaxConverter strips the # prefix requirement — write keys without #.
Detection rule from source code: an array is recognized as a render array when it has exactly ONE of these keys: markup, plain_text, theme, item_list, type (or their #-prefixed versions). Zero or two+ triggers = NOT a render array.
| Type | Trigger key | Use when |
|---|---|---|
| Plain string | — | Simple text content |
html_tag |
type: html_tag |
Wrap content in an HTML element |
component |
type: component |
Embed another SDC component |
| Drupal image | theme: image |
Render an image with alt/dimensions |
| Icon | type: icon |
Render a UI Icons icon in a slot |
| Item list | theme: item_list |
Drupal item list render array |
| YAML list | — (sequence) | Multiple renderables in one slot |
| Raw markup | markup: |
Inject trusted HTML directly |
| Plain text | plain_text: |
Escaped text directly |
Naming Convention
{component_id}.{story_id}.story.yml
- Both segments: lowercase letters, digits, hyphens, underscores
- Convention from ui_suite_daisyui 5.0.x: underscores preferred (
alert_success,image_full,size_xl) - Story ID becomes the
machineName— must be unique within a component scope - File must be in a
components/directory
Pattern
1. Plain string slot
slots:
title: "Card Title"
message: "12 unread messages. Tap to see."
2. type: html_tag
slots:
text:
type: html_tag
tag: p
value: "A card component has a figure, a body, and actions parts"
value must be a plain string — cannot itself be a render array.
3. type: component — Embed another SDC component
slots:
actions:
type: component
component: "ui_suite_daisyui:button"
slots:
label: "Buy Now"
props:
variant: primary
story: sub-key loads an existing story's data as a base:
slots:
center:
type: component
component: ui_suite_daisyui:menu
story: flat # loads menu.flat story data as base
props:
variant: horizontal__md
4. theme: image
slots:
image:
theme: image
uri: "https://img.daisyui.com/images/stock/photo-1606107557195-0e29a4b5b4aa.webp"
alt: Shoes
uri accepts absolute URLs or Drupal stream wrapper paths (public://image.jpg).
5. YAML list — Multiple items in one slot
slots:
buttons:
- type: component
component: "ui_suite_daisyui:button"
slots:
label: Deny
props:
size: sm
- type: component
component: "ui_suite_daisyui:button"
slots:
label: Accept
props:
variant: primary
size: sm
6. library_wrapper
name: Default
library_wrapper: '<div class="w-full bg-base-200 p-4">{{ _story }}</div>'
slots:
label: daisyUI
Use when components need layout context: navbars (full width), drawers (height), cards (max-width constraint).
Complete Working Examples
Alert with icon prop and string slot (verbatim from ui_suite_daisyui 5.0.x)
# alert.alert_success.story.yml
name: Alert success
slots:
message: "12 unread messages. Tap to see."
props:
variant: success
icon:
pack_id: hero_outline_24
icon_id: information-circle
settings:
stroke_width: 2
stroke_color: stroke-success-content
Card with image, mixed title list, and component slots (verbatim from ui_suite_daisyui 5.0.x)
# card.with_badge.story.yml
name: "With badge"
slots:
image:
theme: image
uri: "https://img.daisyui.com/images/stock/photo-1606107557195-0e29a4b5b4aa.webp"
alt: Shoes
title:
- "Card title"
- type: component
component: "ui_suite_daisyui:badge"
slots:
label: "NEW"
props:
variant: secondary
text:
type: html_tag
tag: p
value: "If a dog chews shoes whose shoes does he choose?"
actions:
- type: component
component: "ui_suite_daisyui:badge"
slots:
label: "Fashion"
props:
outline: outline
- type: component
component: "ui_suite_daisyui:badge"
slots:
label: "Products"
props:
outline: outline
props:
variant: compact
heading_level: 2
attributes:
class: ["bg-base-100", "w-96", "shadow-sm"]
Navbar with story: shorthand (verbatim from ui_suite_daisyui 5.0.x)
# navbar.default.story.yml
name: Default
slots:
start:
type: component
component: ui_suite_daisyui:button
slots:
label: daisyUI
props:
variant: ghost
center:
type: component
component: ui_suite_daisyui:menu
story: flat # loads menu.flat story's slots/props as base
props:
variant: horizontal__md
end:
type: component
component: ui_suite_daisyui:button
slots:
label: Button
Drawer with nested components (verbatim from ui_suite_daisyui 5.0.x)
# drawer.default.story.yml
name: Default
slots:
content:
type: component
component: "ui_suite_daisyui:button"
slots:
label: Open drawer
props:
variant: primary
drawer_id: my-drawer-1
attributes:
class: ["drawer-button"]
sidebar:
type: component
component: "ui_suite_daisyui:menu"
props:
variant: vertical__md
items:
- title: "Sidebar Item 1"
url: "#"
- title: "Sidebar Item 2"
- title: "Sidebar Item 3"
url: "#"
attributes:
class: ["bg-base-200", "text-base-content", "min-h-full", "w-80", "p-4"]
props:
variant: default
drawer_id: my-drawer-1
Where Stories Appear
With ui_patterns_library submodule enabled:
- /admin/appearance/ui/components — global overview, all providers
- /admin/appearance/ui/components/{provider} — per-provider overview
- /admin/appearance/ui/components/{provider}/{component_id} — single component page with all stories
If a story has a variant prop, it renders once. If no variant prop, it renders once per defined variant — this behavior is intentional but often surprising.
Common Mistakes
- Wrong: Using
type:andtheme:together in the same YAML mapping → Right: The converter detects render arrays by exactly ONE trigger key. Two = broken output. - Wrong: Writing
library_wrapperas a YAML object → Right: Must be an inline Twig template string:library_wrapper: '<div>{{ _story }}</div>' - Wrong: Confusing icon as a prop vs icon as a slot render array → Right: Prop uses
pack_id/icon_id/settingsat the prop level; slot render array usestype: iconwith the same keys. - Wrong: Putting a render array inside
html_tag'svalue→ Right:valuemust be a plain string. Use a list of html_tag items instead. - Wrong: Omitting
component:when the story file is NOT in the same folder as the component → Right: Cross-provider stories require explicitcomponent: "provider:component_id". - Wrong: Adding
#prefixes manually alongside unprefixed keys → Right: The converter handles#prefixes. Mixing#-prefixed and unprefixed keys in the same array causes the converter to skip that array. - Wrong: Naming story files with hyphens when the component uses underscores → Right: The story_id in the filename must match exactly when referencing via
story:shorthand.
See Also
- Tool Landscape & Decision
- Sub-Theme Stories
- Reference:
modules/contrib/ui_patterns/modules/ui_patterns_library/src/StoriesSyntaxConverter.php— canonical render key list and conversion logic - Reference:
modules/contrib/ui_patterns/modules/ui_patterns_library/src/StoryPluginManager.php— story schema defaults and discovery - Reference:
modules/contrib/ui_patterns/docs/2-authors/1-stories-and-library.md— official story format documentation - Reference:
drupal-ui-patterns.md— full UI Patterns 2 documentation including props schema - Reference:
drupal-ui-suite-daisyui.md— component catalog with all props and slots