Close menu

Pattern Library

Modal

Window sizes

Modal

A dialog overlay that focuses user attention on a task or critical information, blocking interaction with the rest of the page. Follows USWDS modal patterns.

Props

c-modal

Prop Default Description
id Unique ID for the modal element; also used in the trigger button's aria-controls
href Hash link matching the modal id (e.g., "#example-modal")
button_text Text for the trigger button that opens the modal
heading_id ID of the heading element inside the modal for aria-labelledby
body_id ID of the body element inside the modal for aria-describedby
type Set to "large" for a wider modal
is_forced_action "false" Set to "true" to hide the close button, requiring the user to take an explicit action

c-modal.modal-header

Prop Default Description
heading_id ID applied to the heading element inside the header

Sub-components

Sub-component Description
c-modal.modal-header Container for the modal title
c-modal.modal-body Container for the modal body content
c-modal.modal-footer Container for modal action buttons
c-modal.modal-close-button Close/action button within the modal footer

Example Usage

Default Modal

<c-modal
    id="example-modal-1"
    href="#example-modal-1"
    button_text="Open Modal"
    heading_id="modal-1-heading"
    body_id="modal-1-description"
>
    <c-modal.modal-header heading_id="modal-1-heading">
        <h2>Are you sure you want to continue?</h2>
    </c-modal.modal-header>
    <c-modal.modal-body>
        <p id="modal-1-description">You have unsaved changes that will be lost.</p>
    </c-modal.modal-body>
    <c-modal.modal-footer>
        <c-button-group>
            <c-button-group.button-group-item>
                <c-modal.modal-close-button text="Continue without saving" />
            </c-button-group.button-group-item>
            <c-button-group.button-group-item>
                <c-modal.modal-close-button
                    text="Go back"
                    type="unstyled"
                    extra_classes="padding-105 text-center"
                />
            </c-button-group.button-group-item>
        </c-button-group>
    </c-modal.modal-footer>
</c-modal>

Large Modal

<c-modal
    id="example-modal-2"
    type="large"
    href="#example-modal-2"
    button_text="Open Large Modal"
    heading_id="modal-2-heading"
    body_id="modal-2-description"
>
    <c-modal.modal-header heading_id="modal-2-heading">
        <h2>Are you sure you want to continue?</h2>
    </c-modal.modal-header>
    <c-modal.modal-body>
        <p id="modal-2-description">You have unsaved changes that will be lost.</p>
    </c-modal.modal-body>
    <c-modal.modal-footer>
        <c-button-group>
            <c-button-group.button-group-item>
                <c-modal.modal-close-button text="Continue without saving" />
            </c-button-group.button-group-item>
            <c-button-group.button-group-item>
                <c-modal.modal-close-button
                    text="Go back"
                    type="unstyled"
                    extra_classes="padding-105 text-center"
                />
            </c-button-group.button-group-item>
        </c-button-group>
    </c-modal.modal-footer>
</c-modal>

Forced Action Modal

The close button is hidden; users must choose an explicit action.

<c-modal
    id="example-modal-3"
    href="#example-modal-3"
    button_text="Open modal with forced action"
    heading_id="modal-3-heading"
    body_id="modal-3-description"
    is_forced_action="true"
>
    <c-modal.modal-header heading_id="modal-3-heading">
        <h2>Your session will end soon.</h2>
    </c-modal.modal-header>
    <c-modal.modal-body>
        <p id="modal-3-description">
            You've been inactive for too long. Please choose to stay signed in
            or sign out. Otherwise, you'll be signed out automatically in 5 minutes.
        </p>
    </c-modal.modal-body>
    <c-modal.modal-footer>
        <c-button-group>
            <c-button-group.button-group-item>
                <c-modal.modal-close-button text="Yes, stay signed in" />
            </c-button-group.button-group-item>
            <c-button-group.button-group-item>
                <c-modal.modal-close-button
                    text="Sign out"
                    type="unstyled"
                    extra_classes="padding-105 text-center"
                />
            </c-button-group.button-group-item>
        </c-button-group>
    </c-modal.modal-footer>
</c-modal>

Usage Notes

  • Each modal on a page must have a unique id; the href must match that id with a leading #
  • Pass matching heading_id to both c-modal (for aria-labelledby) and c-modal.modal-header (for the id on the heading element)
  • Pass matching body_id to both c-modal (for aria-describedby) and the <p> inside c-modal.modal-body
  • USWDS JavaScript is required to activate the modal show/hide behavior
  • Use is_forced_action="true" only for critical decisions; always provide at least one actionable button to dismiss the modal
{% load cotton %}

<div class="component-variants">
    <h1>Modal Component</h1>
    
    {% for variant in variants %}
    <div class="component-variant">
        <h2 class="variant-title">{{ variant.title }}</h2>

        {% cotton modal id="{{ variant.id }}" type="{{ variant.type }}" href="{{ variant.href }}" button_text="{{ variant.button_text }}" is_forced_action="{{ variant.is_forced_action }}" body_id="{{ variant.body_id }}" heading_id="{{ variant.heading_id }}" %}
            {% cotton modal.modal-header heading_id="{{ variant.heading_id }}" %}
                <h2>{{ variant.heading_text }}</h2>
            {% endcotton %}
            {% cotton modal.modal-body %}
                <p id="{{ variant.body_id }}">
                    {{ variant.body_text }}
                </p>
            {% endcotton %}
            {% cotton modal.modal-footer %}
                {% cotton button-group %}   
                    {% for button in variant.footer %}
                    {% cotton button-group.button-group-item %}
                        {% cotton modal.modal-close-button text="{{ button.text }}" type="{{ button.type }}" disabled="{{ button.disabled }}" extra_classes="{{ button.extra_classes }}" %}{% endcotton %}
                    {% endcotton %}
                    {% endfor %}
                {% endcotton %}
            {% endcotton %}
        {% endcotton %}
    </div>
    {% endfor %}
</div>
name: Modal
context:
  variants:            
    - title: "Default Modal"
      id: "example-modal-1"
      href: "#example-modal-1"
      button_text: "Open Modal"
      heading_id: "modal-1-heading"
      heading_text: "Are you sure you want to continue?"
      body_id: "modal-1-description"
      body_text: "You have unsaved changes that will be lost."
      footer:
      - text: "Continue without saving"
      - text: "Go back"
        type: "unstyled"
        extra_classes: "padding-105 text-center"
    - title: "Large Modal"
      id: "example-modal-2"
      type: "large"
      href: "#example-modal-2"
      button_text: "Open Large Modal"
      heading_id: "modal-2-heading"
      heading_text: "Are you sure you want to continue?"
      body_id: "modal-2-description"
      body_text: "You have unsaved changes that will be lost."
      footer:
      - text: "Continue without saving"
      - text: "Go back"
        type: "unstyled"
        extra_classes: "padding-105 text-center"
    - title: "Forced Action Modal"
      id: "example-modal-3"
      href: "#example-modal-3"
      button_text: "Open modal with forced action"
      heading_id: "modal-3-heading"
      heading_text: "Your session will end soon."
      body_id: "modal-3-description"
      body_text: "You’ve been inactive for too long. Please choose to stay signed in
            or sign out. Otherwise, you’ll be signed out automatically in 5
            minutes."
      is_forced_action: "true"
      footer:
      - text: "Yes, stay signed in"
      - text: "Sign out"
        type: "unstyled"
        extra_classes: "padding-105 text-center"