Welcome to Vanilla Breeze
This bell pulls live notifications from /go/notify/messages — the same contract documented at /docs/concepts/service-contracts/. Static articles like this one are the no-JS / no-backend fallback.
This bell pulls live notifications from /go/notify/messages — the same contract documented at /docs/concepts/service-contracts/. Static articles like this one are the no-JS / no-backend fallback.
Horizontal user story map with activity columns and drag-and-drop between columns.
A web component that renders a horizontal user story map organized by activity columns. Each column contains a <drag-surface> enabling drag-and-drop reorder within a column and transfer between columns. Ideal for sprint planning, backlog grooming, and mapping user activities to deliverable stories.
<story-map> <section data-activity="discovery" data-activity-label="Discovery"> <article draggable="true" data-id="PROJ-201">Interview target users</article> <article draggable="true" data-id="PROJ-202">Competitive analysis</article> <article draggable="true" data-id="PROJ-203">Survey existing customers</article> </section> <section data-activity="design" data-activity-label="Design"> <article draggable="true" data-id="PROJ-204">Create wireframes</article> <article draggable="true" data-id="PROJ-205">Design component library</article> </section> <section data-activity="development" data-activity-label="Development"> <article draggable="true" data-id="PROJ-206">Build API endpoints</article> <article draggable="true" data-id="PROJ-207">Implement dashboard UI</article> <article draggable="true" data-id="PROJ-208">Add keyboard shortcuts</article> </section> <section data-activity="testing" data-activity-label="Testing"> <article draggable="true" data-id="PROJ-209">Write integration tests</article> <article draggable="true" data-id="PROJ-210">User acceptance testing</article> </section></story-map>
| Attribute | Type | Default | Description |
|---|---|---|---|
src |
string (URL) | — | Path to a JSON file containing activity and story data |
compact |
boolean | — | Reduces column widths, padding, and header font sizes for denser layouts |
title |
string | — | Optional heading displayed above the story map |
The <story-map> component expects direct <section> children, each defining an activity column. Story items go inside each section.
| Attribute | Type | Default | Description |
|---|---|---|---|
data-activity |
string | — | Activity column identifier. Used in event details and as the internal key. |
data-activity-label |
string | Title-cased data-activity |
Display label for the column header. Falls back to a title-cased version of the activity ID. |
data-journey-phase |
string | — | Optional link to a <user-journey> phase name for cross-referencing |
Items inside each section become draggable story cards. Use <article> elements for simple text or <user-story> elements for rich story cards.
| Attribute | Type | Description |
|---|---|---|
draggable |
boolean | Must be set to "true" for drag capability |
data-id |
string | Stable identifier for the item, included in event details |
Each activity column consists of a sticky header and a drag surface area:
<drag-surface> element that holds the story items. All surfaces share the same group identifier, enabling cross-column transfers.The columns sit inside a horizontally scrollable container. Each column has a minimum width of 14rem (11rem in compact mode) and a maximum of 22rem, using flex: 1 0 14rem to fill available space while allowing horizontal scroll when columns exceed the viewport.
Items can be reordered within a column and transferred between columns:
story-map:reorder event with the old and new indices.story-map:transfer event.<drag-surface> provides keyboard reorder and cross-surface transfer using its built-in keyboard shortcuts.const map = document.querySelector('story-map'); map.addEventListener('story-map:transfer', (e) => { console.log(`${e.detail.itemId} moved from ${e.detail.fromActivity} to ${e.detail.toActivity}`);}); map.addEventListener('story-map:reorder', (e) => { console.log(`${e.detail.itemId} reordered in ${e.detail.activity}: ${e.detail.oldIndex} -> ${e.detail.newIndex}`);});
Load the entire story map from a JSON file using the src attribute. The component creates activity columns with <user-story> cards automatically.
<story-map src="/data/story-map.json"></story-map>
{ "activities": [ { "id": "discovery", "label": "Discovery", "journeyPhase": "awareness", "stories": [ { "id": "PROJ-101", "title": "User interviews", "persona": "Researcher", "action": "interview 10 target users", "priority": "high", "status": "done", "points": "5" } ] }, { "id": "design", "label": "Design", "stories": [ { "id": "PROJ-201", "title": "Dashboard wireframes", "persona": "Designer", "action": "create wireframes for new dashboard", "priority": "high", "status": "in-progress", "points": "5" } ] } ]}
For richer story cards, use <user-story> elements as children instead of plain <article> elements. Add the compact attribute on each story for a tighter layout within the columns.
<story-map> <section data-activity="discovery" data-activity-label="Discovery"> <user-story draggable="true" data-id="PROJ-201" story-id="PROJ-201" priority="high" status="in-progress" points="3" compact> <span slot="persona">Sarah Chen</span> <span slot="action">interview target users about dashboard needs</span> </user-story> </section> <section data-activity="development" data-activity-label="Development"> <user-story draggable="true" data-id="PROJ-206" story-id="PROJ-206" priority="high" status="to-do" points="8" compact> <span slot="persona">Alex Rivera</span> <span slot="action">build API endpoints for timeline data</span> </user-story> </section></story-map>
| Event | Detail | When |
|---|---|---|
story-map:reorder |
{ itemId, activity, oldIndex, newIndex } |
Fires when an item is reordered within the same activity column |
story-map:transfer |
{ itemId, fromActivity, toActivity, newIndex } |
Fires when an item is dragged from one activity column to another |
story-map:ready |
{ activityCount, storyCount } |
Fires after the component initializes with the total number of activity columns and stories |
role="region" with aria-label="Story map" and is keyboard-focusable via tabindex="0"<drag-surface> has an aria-label describing the activity column (e.g., "Discovery stories")aria-live="polite" announces item transfers between columns<drag-surface> componentsprefers-reduced-motion: reduce by disabling scroll behavior transitionscontainer-type: inline-size and stacks columns vertically at narrow viewports (<30rem)break-inside: avoid on columns<user-story> — Rich story cards that work as draggable items in columns<impact-effort> — Prioritization matrix for triaging stories<empathy-map> — Empathy maps for user research informing stories<user-journey> — Journey maps linked via data-journey-phase<user-persona> — Persona cards providing context for story owners<drag-surface> — The underlying drag-and-drop primitive used by each column