Skip to content
Open
5 changes: 5 additions & 0 deletions .changeset/sequence-diagram-styling.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'mermaid': minor
---

feat(sequence): add style, classDef, and class support for sequence diagram actors (#523)
106 changes: 106 additions & 0 deletions cypress/integration/rendering/sequence/sequencediagram.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1250,4 +1250,110 @@ describe('Sequence diagram', () => {
}
);
});

describe('actor styling and classDef', () => {
it('should render an actor with an inline style', () => {
imgSnapshotTest(
`
sequenceDiagram
participant Alice
participant Bob
style Alice fill:#f9f,stroke:#333,stroke-width:2px
Alice->>Bob: Hello
Bob-->>Alice: Hi back
`
);
});

it('should render multiple actors with independent inline styles', () => {
imgSnapshotTest(
`
sequenceDiagram
participant Alice
participant Bob
participant Carol
style Alice fill:#bbf,stroke:#00f
style Bob fill:#fbb,stroke:#f00
style Carol fill:#bfb,stroke:#0f0
Alice->>Bob: One
Bob->>Carol: Two
Carol-->>Alice: Three
`
);
});

it('should render a classDef defined but not yet attached without changing layout', () => {
// Defining a classDef but never attaching it should be a no-op visually,
// matching how flowchart classDef works.
imgSnapshotTest(
`
sequenceDiagram
classDef highlighted fill:#f9f,stroke:#333,stroke-width:2px
participant Alice
participant Bob
Alice->>Bob: Hello
`
);
});

it('should render a classDef attached via the class keyword', () => {
imgSnapshotTest(
`
sequenceDiagram
classDef highlighted fill:#f9f,stroke:#333,stroke-width:2px
participant Alice
participant Bob
class Alice highlighted
Alice->>Bob: Hello
Bob-->>Alice: Hi
`
);
});

it('should render a class attached to multiple actors', () => {
imgSnapshotTest(
`
sequenceDiagram
classDef important fill:#fbb,stroke:#f00,stroke-width:3px
participant Alice
participant Bob
participant Carol
class Alice,Carol important
Alice->>Bob: Hello
Bob->>Carol: Forwarding
`
);
});

it('should render styled actors inside a box', () => {
imgSnapshotTest(
`
sequenceDiagram
box Customer Side
participant Alice
participant Bob
end
style Alice fill:#bbf,stroke:#00f
style Bob fill:#fbb,stroke:#f00
Alice->>Bob: Inside the box
`
);
});

it('should render styled actors with the dark theme', () => {
imgSnapshotTest(
`
%%{init: {'theme': 'dark'}}%%
sequenceDiagram
classDef highlighted fill:#665,stroke:#fa0,stroke-width:2px
participant Alice
participant Bob
class Alice highlighted
style Bob fill:#446,stroke:#88f
Alice->>Bob: Hello in dark mode
Bob-->>Alice: Hi back
`
);
});
});
});
52 changes: 52 additions & 0 deletions docs/syntax/sequenceDiagram.md
Original file line number Diff line number Diff line change
Expand Up @@ -1158,6 +1158,58 @@ text.actor {
}
```

### Per-actor styling (v\<MERMAID_RELEASE_VERSION>+)

Individual actors can be styled inline or by attaching a reusable class. This addresses [issue #523](https://github.com/mermaid-js/mermaid/issues/523), the long-standing request to support per-actor colors in sequence diagrams.

**Inline `style` statement.** Apply styles to a single actor by name:

```mermaid-example
sequenceDiagram
participant Alice
participant Bob
style Alice fill:#f9f,stroke:#333,stroke-width:2px
Alice->>Bob: Hello
Bob-->>Alice: Hi back
```

```mermaid
sequenceDiagram
participant Alice
participant Bob
style Alice fill:#f9f,stroke:#333,stroke-width:2px
Alice->>Bob: Hello
Bob-->>Alice: Hi back
```

**Reusable `classDef` and `class` statements.** Define a class once and attach it to one or more actors. This mirrors the `classDef` / `class` pattern used in flowchart diagrams:

```mermaid-example
sequenceDiagram
classDef important fill:#fbb,stroke:#f00,stroke-width:3px
participant Alice
participant Bob
participant Carol
class Alice,Carol important
Alice->>Bob: Hello
Bob->>Carol: Forwarding
```

```mermaid
sequenceDiagram
classDef important fill:#fbb,stroke:#f00,stroke-width:3px
participant Alice
participant Bob
participant Carol
class Alice,Carol important
Alice->>Bob: Hello
Bob->>Carol: Forwarding
```

The supported declarations include `fill`, `stroke`, `stroke-width`, `stroke-dasharray`, `opacity`, and the `color` and `font-*` properties (which are routed to the actor's text element). Declarations are sanitized — values containing `url(...)`, `expression(...)`, `behavior:`, `javascript:`, `@import`, or rule terminators are dropped, so user-supplied styles cannot reach external resources or break out of the diagram's scoped stylesheet.

This first release covers actor boxes only. Styling for arrows, notes, and loops is planned as a follow-up.

## Configuration

It is possible to adjust the margins for rendering the sequence diagram.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

// Special states for recognizing aliases
// A special state for grabbing text up to the first comment/newline
%x ID ALIAS LINE CONFIG CONFIG_DATA
%x ID ALIAS LINE STYLE_STMT CONFIG CONFIG_DATA

%x acc_title
%x acc_descr
Expand Down Expand Up @@ -56,6 +56,11 @@
"option" { this.begin('LINE'); return 'option'; }
"break" { this.begin('LINE'); return 'break'; }
<LINE>(?:[:]?(?:no)?wrap:)?[^#\n;]* { this.popState(); return 'restOfLine'; }
<STYLE_STMT>((?!\n)\s)+ /* skip same-line whitespace */
<STYLE_STMT>[^\n;]* { this.popState(); return 'styleRestOfLine'; }
classDef(?=\s) { this.begin('STYLE_STMT'); return 'classDef'; }
style(?=\s) { this.begin('STYLE_STMT'); return 'style'; }
class(?=\s) { this.begin('STYLE_STMT'); return 'class'; }
"end" return 'end';
"left of" return 'left_of';
"right of" return 'right_of';
Expand Down Expand Up @@ -188,6 +193,9 @@ statement
| acc_title acc_title_value { $$=$2.trim();yy.setAccTitle($$); }
| acc_descr acc_descr_value { $$=$2.trim();yy.setAccDescription($$); }
| acc_descr_multiline_value { $$=$1.trim();yy.setAccDescription($$); }
| 'classDef' styleRestOfLine { var m = $2.trim().match(/^(\S+)\s+([\s\S]*)/); if(m) { yy.addClass(m[1], [m[2]]); } }
| 'style' styleRestOfLine { var m = $2.trim().match(/^(\S+)\s+([\s\S]*)/); if(m) { $$ = {type: 'applyStyle', actor: m[1], styleStr: [m[2]]}; } }
| 'class' styleRestOfLine { var m = $2.trim().match(/^(\S+)\s+([\s\S]+)/); if(m) { $$ = {type: 'applyClass', actor: m[1], className: m[2].trim()}; } }
| 'loop' restOfLine document end
{
$3.unshift({type: 'loopStart', loopText:yy.parseMessage($2), signalType: yy.LINETYPE.LOOP_START});
Expand Down
Loading
Loading