All posts
MermaidUMLClass DiagramsDomain ModelingSoftware Design

Mermaid UML diagrams: class, sequence, and state modeling

8 min readThe MermaidCreator team

UML has long been the language of software design—class diagrams for domain models, sequence diagrams for interactions, state diagrams for workflows. But UML tools were expensive and cumbersome. Mermaid brings UML into the modern age: draw diagrams in text, version them in git, and embed them in any doc.

This guide unifies three core UML diagram types in Mermaid and shows how to use them together for end-to-end design documentation.

Why UML matters (and why Mermaid makes it practical)

UML standardizes how teams think about design:

  • Class diagrams show what entities exist and how they relate
  • Sequence diagrams show when and how entities interact
  • State diagrams show how entities change over time

Traditional UML tools require special software, export to proprietary formats, and rot in a shared drive. Mermaid UML is text, so it:

  • Lives next to code in git
  • Diffs clearly in pull requests
  • Embeds anywhere (README, docs, design docs)
  • Survives tool changes (Lucidchart closes? Your diagrams are still text)

Class diagrams: domain modeling

A class diagram models your domain—entities, their attributes, methods, and relationships. It's the blueprint for your data model and API schema.

Here's a simple e-commerce domain:

classDiagram
    class User {
        int id
        string email
        string password_hash
        datetime created_at
        register(email, password)
        login(email, password)
    }
    
    class Order {
        int id
        int user_id
        decimal total
        string status
        datetime created_at
        add_item(product, quantity)
        calculate_total()
        ship()
    }
    
    class Product {
        int id
        string name
        decimal price
        int stock
        update_stock(quantity)
    }
    
    class OrderItem {
        int order_id
        int product_id
        int quantity
        decimal unit_price
    }
    
    User "1" -- "*" Order: places
    Order "1" -- "*" OrderItem: contains
    OrderItem "*" -- "1" Product: includes

This diagram says:

  • A User can place many Orders (1:many)
  • An Order contains many OrderItems (1:many)
  • An OrderItem references one Product (many:1)
  • Each class has attributes and methods

Reading the diagram:

  • Box name = class name
  • Top section = attributes (with types)
  • Bottom section = methods (public functions)
  • Lines = relationships (cardinality: 1 = one, * = many)

Relationships in UML

RelationshipSyntaxMeaning
Association--one class references another
Inheritance<|--child extends parent
Composition*--parent owns child (child can't exist alone)
Aggregationo--parent contains child (child can exist alone)
Dependency..>class uses another temporarily

Example:

classDiagram
    class Animal {
        string name
        eat()
        sleep()
    }
    
    class Dog {
        bark()
    }
    
    class Cat {
        meow()
    }
    
    class Person {
        Pet pet
        feed(pet)
    }
    
    Dog --|> Animal: inherits
    Cat --|> Animal: inherits
    Person "*" -- "1" Animal: owns

A Dog and Cat both inherit from Animal (they are-a Animal). A Person owns an Animal (they have-a Animal). Use this diagram in your design docs to clarify inheritance and ownership.

Antipattern: too many classes in one diagram

A class diagram with 30+ classes is hard to read. Break it into layers:

classDiagram
    class UserDTO {
        int id
        string email
    }
    
    class User {
        int id
        string email
        Password password
        register()
    }
    
    class Password {
        string hash
        verify(plaintext)
    }
    
    UserDTO ..> User: maps to
    User "1" -- "1" Password: contains

Domain layer (core entities) separate from API layer (DTOs). Clarifies what crosses the API boundary.

Sequence diagrams: interaction modeling

A sequence diagram shows when entities communicate. It's the "happy path" through your system—actors and their messages over time.

Here's a user login flow:

sequenceDiagram
    participant U as User
    participant App
    participant Auth as Auth Service
    participant DB as Database
    
    U->>App: click "Log in"
    App->>App: open login form
    U->>App: enter email + password
    App->>Auth: POST /login (email, password)
    Auth->>Auth: hash password
    Auth->>DB: SELECT * FROM users WHERE email = ?
    DB-->>Auth: user record (with password_hash)
    Auth->>Auth: compare hashes
    alt Hashes match
        Auth->>Auth: generate JWT
        Auth-->>App: 200 OK (token)
        App->>U: redirect to /dashboard
    else Hashes don't match
        Auth-->>App: 401 Unauthorized
        App->>U: show "Invalid credentials"
    end

Reading the sequence diagram:

  • Top boxes = actors (User, App, Auth Service, Database)
  • Arrows ->> = request (solid line)
  • Dashed arrows --> = response
  • Boxes around steps = logic or loops (alt, loop, par, etc.)
  • Time flows top to bottom — first message at top, last at bottom

Sequence diagram patterns

Happy path only:

sequenceDiagram
    Client->>API: POST /orders
    API->>DB: insert order
    DB-->>API: order_id
    API-->>Client: 200 OK

With error handling:

sequenceDiagram
    Client->>API: POST /orders
    API->>Validator: validate request
    alt Valid
        Validator-->>API: OK
        API->>DB: insert
        DB-->>API: order_id
        API-->>Client: 200
    else Invalid
        Validator-->>API: error
        API-->>Client: 400 Bad Request
    end

With retries:

sequenceDiagram
    Client->>API: request
    loop Retry up to 3 times
        API->>Service: call service
        alt Service responds
            Service-->>API: result
            break Exit on success
        else Service timeout
            API->>API: wait, then retry
        end
    end
    API-->>Client: result

Use alt, loop, break, par to model control flow. Keeps the happy path readable while handling edge cases.

Antipattern: too many actors or messages

A sequence diagram with 15+ participants or 50+ messages is a wall of text. Break it into smaller flows:

  • User login → separate diagram
  • Order creation → separate diagram
  • Payment processing → separate diagram

Link them: "After login (see [User login sequence]), the user creates an order (see [Order creation sequence])."

State diagrams: workflow modeling

A state diagram models how an entity changes over time. It's essential for entities with clear states: orders, users, payments, subscriptions.

Here's an order lifecycle:

stateDiagram-v2
    [*] --> Pending
    Pending --> Confirmed: payment received
    Confirmed --> Shipped: ready to go
    Shipped --> Delivered: arrives at address
    Delivered --> [*]
    
    Pending --> Cancelled: user cancels
    Confirmed --> Cancelled: user cancels
    Cancelled --> [*]
    
    Shipped --> Returned: customer returns
    Returned --> Refunded: process refund
    Refunded --> [*]

Reading the state diagram:

  • Boxes = states (Pending, Confirmed, Shipped)
  • Arrows = transitions (labeled with the event that triggers them)
  • [*] = start and end

This diagram tells reviewers: "An order starts as Pending, moves to Confirmed on payment, then Shipped, then Delivered. At any point before Shipped, it can be Cancelled. After Shipped, it can be Returned for a refund."

Modeling guards and actions

Some transitions are conditional. Use guards (in brackets):

stateDiagram-v2
    [*] --> Pending
    Pending --> Confirmed: payment_received
    Confirmed --> Processing: [stock available]
    Confirmed --> Backorder: [stock unavailable]
    Processing --> Shipped: ready
    Backorder --> Processing: stock restocked
    Shipped --> Delivered: arrives
    Delivered --> [*]

Guard [stock available] means the transition only happens if that condition is true.

Use actions to show what happens during a transition:

stateDiagram-v2
    [*] --> Init
    Init --> Active: activate() / send_welcome_email()
    Active --> Suspended: suspend() / log_event()
    Suspended --> Active: reactivate()
    Active --> Inactive: deactivate() / archive()
    Inactive --> [*]

activate() / send_welcome_email() means: when activate() is called, send a welcome email. Keeps the code-to-diagram mapping clear.

Bringing it together: UML design flow

For a complete design, model all three UML types together:

  1. Class diagram — entities and domain model
  2. Sequence diagram — happy path through the system
  3. State diagram — entity lifecycle

Example: designing a subscription system.

Class diagram:

classDiagram
    class Subscription {
        int id
        string plan
        datetime start
        datetime end
        string status
        activate()
        pause()
        cancel()
        renew()
    }
    
    class Plan {
        string name
        decimal price
        list features
    }
    
    class User {
        int id
        Subscription subscription
    }
    
    User "1" -- "0..1" Subscription: has
    Subscription "*" -- "1" Plan: uses

Sequence diagram (activate):

sequenceDiagram
    User->>App: click "Activate"
    App->>Stripe: create subscription
    Stripe-->>App: subscription_id
    App->>DB: INSERT subscription
    DB-->>App: success
    App->>Email: send activation email
    App-->>User: redirect to dashboard

State diagram (lifecycle):

stateDiagram-v2
    [*] --> Pending
    Pending --> Active: activate()
    Active --> Paused: pause()
    Paused --> Active: resume()
    Active --> Cancelled: cancel()
    Cancelled --> [*]

Together, these three diagrams paint a complete picture:

  • What entities exist (classes)
  • How they interact (sequence)
  • How they change (state)

FAQ

Should I write the class diagram first or the sequence diagram?
Start with the domain (class diagram) to model entities. Then sequence diagrams to show how they interact. Then state diagrams for entity lifecycles. They feed each other—often you'll iterate.

How detailed should a class diagram be?
Include attributes and relationships that your team needs to understand the design. Don't include every private method or every attribute. Use the diagram as a north star, not a replica of the code.

Can I show multiple sequences for different paths (happy path, error path, etc.)?
Yes, but keep them in separate diagrams and link them. One sequence per main flow keeps it readable.

Should I update diagrams when the code changes?
Yes. Treat them as living docs. If an entity gains a new relationship or a flow changes, update the diagram in the same PR. This is why versioning them in git matters.

How do I enforce these UML conventions on my team?
Document your conventions in a DESIGN.md file (class diagram structure, naming, what to include). Share templates (see Mermaid reusable diagram components) so everyone starts from the same base.

Master UML modeling with Mermaid. Draft your domain model in the MermaidCreator editor and let your next design review start with a clear diagram.

Related posts