Skip to main content

Generate user story maps from markdown

Project description

storymap

Generate user story maps from markdown. Write your product spec as a readable document — personas, releases, activities, tasks, and stories — and render it as a styled, interactive HTML page.

Installation

pip install storymap

Quick start

storymap init                   # create a skeleton storymap.md
storymap init myproduct.md      # create a named skeleton
storymap render mymap.md        # → mymap.html (same directory)
storymap render mymap.md -o out # → out/mymap.html

The output is a single self-contained HTML file. Local images referenced in story descriptions are embedded as base64 data URIs — no external files needed.

PDF output: open the generated HTML in a browser and use print-to-PDF (Ctrl+P → Save as PDF). The HTML includes print-optimised CSS for landscape layout and color preservation.

Document format

A storymap file is a standard markdown document with three reserved top-level sections: # Releases, # Personas, and # Map. Any other # heading is treated as the document title (first one) or ignored.

# My Product
Short description.

# Releases
## MVP
First public release.

## Beta
Invite-only beta with selected users.

# Personas
## Margie the Manager
- **Age:** 45–55
- **Tech level:** Low

Margie manages a team of 8 and primarily uses the app on mobile.

# Map
## User Management
### Authentication
#### Sign in [status:: done] [persona:: Margie the Manager] [release:: MVP]
User can log in with email and password.

#### Password reset [status:: in-progress] [deadline:: 2026-03-01] [release:: Beta]

### Profile
#### Edit profile [status:: done] [release:: MVP]

#### Upload avatar [status:: blocked] [release:: Beta]
Blocked pending storage provider decision.

Sections

Section Required Purpose
# Releases Yes Defines release swimlanes
# Personas No UX persona descriptions
# Map Yes The story map itself

Section names are case-insensitive.

Map hierarchy

## Activity       (column group)
### Task          (column)
#### Story        (card in a swimlane)

Assigning stories to releases

Use the [release:: name] field on each story to assign it to a release swimlane. The name must match a release defined in the # Releases section. Stories without a [release::] field are parsed but not shown in any swimlane.

### Authentication
#### Sign in [status:: done] [release:: MVP]
#### Remember me [status:: done] [release:: MVP]
#### SSO [status:: not-started] [release:: Beta]
#### SAML [status:: not-started] [release:: GA]

This is more explicit than positional separators — each story carries its own release assignment regardless of order in the file.

Story fields

Stories support optional inline fields using [key:: value] syntax. Fields appear as badges on the rendered story card.

#### Story name [status:: done] [persona:: Margie the Manager] [deadline:: 2026-03-01] [release:: MVP]
Field Values Default
status not-started, in-progress, done, blocked not-started
release Release name from # Releases section
persona Any string matching a persona name
deadline ISO date YYYY-MM-DD

Any other [key:: value] field is accepted and rendered as a badge.

Story descriptions

Markdown content following a #### Story heading and before the next heading is treated as the story description. Descriptions support standard markdown: bold, italics, links, lists, and images.

The first paragraph is always visible on the story card. Additional paragraphs (separated by a blank line) are shown only in Detail zoom level.

#### Sign in [status:: done] [release:: MVP]
User can log in with email and password.

**Acceptance criteria:**
- Given valid credentials, user is redirected to dashboard
- Given invalid credentials, an error message is shown

![wireframe](./screens/sign-in.png)

Images

Images in story descriptions and persona descriptions are embedded as base64 data URIs in the output HTML, making the file fully self-contained. Paths are resolved relative to the source .md file. Remote URLs are left as-is.

Interactive HTML features

The rendered HTML includes controls for navigating large maps:

Zoom levels — three buttons in the sticky header:

  • Overview — story names only, compact layout
  • Map — story names, status badges, and first-paragraph descriptions
  • Detail — full descriptions and acceptance criteria expanded

Release focus — a dropdown to highlight one release swimlane and dim the rest. Works independently of the zoom level.

Story Lens — a toggle that enables click-to-zoom on individual story cards. When active, clicking a card expands it to ~40% of the viewport width with full details visible. Clicking outside collapses it. Stories in the focused release (if any) are the only ones that can be expanded.

CLI reference

Usage: storymap [OPTIONS] COMMAND [ARGS]...

  Generate user story maps from markdown.

Options:
  --version  Show the version and exit.
  --help     Show this message and exit.

Commands:
  init    Create a skeleton storymap markdown file to get started.
  render  Render a story map markdown INPUT_FILE to HTML.

storymap render

Usage: storymap render [OPTIONS] INPUT_FILE

Options:
  -o, --output DIR                Output directory. Defaults to the input
                                  file's directory.
  -t, --template FILE             Path to a custom Jinja2 template (.html.j2).
  --status-colors KEY=COLOR,...   Override status colors.
                                  Example: done=#00FF00,blocked=#FF0000
  --ui-colors KEY=COLOR,...       Override UI colors.
                                  Example: activity=#1565C0,task=#90CAF9
  --help                          Show this message and exit.

storymap init

Usage: storymap init [OUTPUT_FILE]

  OUTPUT_FILE defaults to storymap.md in the current directory.
  Refuses to overwrite an existing file.

Customisation

Color overrides

storymap render mymap.md \
  --status-colors "done=#27AE60,in-progress=#2980B9,blocked=#E74C3C" \
  --ui-colors "activity=#2C3E50,task=#34495E"

Default status colors:

Status Color
not-started #E0E0E0 grey
in-progress #90CAF9 blue
done #A5D6A7 green
blocked #EF9A9A red

Custom templates

storymap render mymap.md --template my-template.html.j2

The template receives:

Variable Type Description
document StorymapDocument The full parsed document
status_colors dict[str, str] Resolved status → hex color
ui_colors dict[str, str] Resolved UI element → hex color
render_md callable Render a markdown string to HTML
render_md_intro callable Render first paragraph only
render_md_rest callable Render everything after first paragraph

The darken filter is also available: {{ color | darken }}.

Development

Requires pipenv and just.

git clone https://github.com/mozaicworks/storymap
cd storymap
just install
just test

Project structure

storymap/
├── model.py       — dataclasses and default color constants
├── parser.py      — markdown-it-py state machine parser
├── renderer.py    — Jinja2 HTML renderer with base64 image embedding
├── cli.py         — click CLI entry point
└── templates/
    └── default.html.j2
tests/
├── test_model.py
├── test_parser.py
├── test_renderer.py
└── test_cli.py

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

storymap-0.1.3.tar.gz (29.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

storymap-0.1.3-py3-none-any.whl (19.8 kB view details)

Uploaded Python 3

File details

Details for the file storymap-0.1.3.tar.gz.

File metadata

  • Download URL: storymap-0.1.3.tar.gz
  • Upload date:
  • Size: 29.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.6

File hashes

Hashes for storymap-0.1.3.tar.gz
Algorithm Hash digest
SHA256 6360f7a3124e277eed6d636fc2a063c0626400c0c6ab39988ba657eaa9c35871
MD5 4e487303d05cd4720e9a57a0ee9e9e9e
BLAKE2b-256 d75fccbaa1fcb001c09a7eee8ed94dfb03a3c239fe1d0fbd0340f327817c1301

See more details on using hashes here.

File details

Details for the file storymap-0.1.3-py3-none-any.whl.

File metadata

  • Download URL: storymap-0.1.3-py3-none-any.whl
  • Upload date:
  • Size: 19.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.6

File hashes

Hashes for storymap-0.1.3-py3-none-any.whl
Algorithm Hash digest
SHA256 707fd62ba034f39447c08228ff016221c94488b4b249616ad26ba5ae86d9e0c2
MD5 bbba0236745752d885c6dbe4b24af1ae
BLAKE2b-256 6ec4aaa235b5ce011d420fb6fb5eb88acbbbc4d007e7ef723814693835ad7ca7

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page