Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions docs/devupdate.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,21 @@ The `moodle_page` class now includes `set_supplementary_content()` and `get_supp
For instance, `mod_forum` uses this new mechanism to display a "Go to all discussions" link in the sticky footer when viewing an individual discussion.

For more information, see the [Adding supplementary content to the sticky footer](./apis/plugintypes/format/linear_navigation.md#adding-supplementary-content-to-the-sticky-footer) section.

## Modal dialogue titles are now `<h2>` elements

<Since version="5.3" issueNumber="MDL-75699" />

The title rendered by the `core/modal` template is now an `<h2 class="modal-title fs-5">` instead of an `<h5>`, so that opening a dialogue no longer breaks the page's heading hierarchy for assistive technology users. The title's appearance is unchanged, because its size is now set by the `fs-5` utility class rather than by the element.

If your plugin renders headings inside dialogue content, they must be nested beneath this `<h2>`, so the first level available to you is `<h3>`. Headings that previously nested beneath the old `<h5>` will now skip levels. This includes content that is rendered into a dialogue without being authored as part of one -- for example, the Markdown headings in an activity module's `modulename_help` string, which core has lowered from `######` to `####` for this reason.

If your plugin renders its own modal header markup, or overrides the `header` block of the `core/modal` template, apply the same `<h2 class="modal-title fs-5">` pattern.

:::note[Backported]

This change has also been backported to Moodle 4.5, 5.1, and 5.2. On Moodle 4.5 the title carries the Bootstrap 4 `h5` utility class rather than `fs-5`.

:::

For more information, see [Heading structure](./guides/javascript/modal/index.md#heading-structure) in the Modal Dialogues guide.
88 changes: 88 additions & 0 deletions docs/guides/javascript/modal/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,94 @@ export const init = async () => {
</TabItem>
</Tabs>

## Heading structure {/* #heading-structure */}

The `core/modal` template renders the dialogue title as a level 2 heading:

```mustache title="The title element in core/modal"
<h2 id="{{uniqid}}-modal-title" class="modal-title fs-5 text-truncate" data-region="title">Test title</h2>
```

The level is deliberately fixed at `<h2>`. A dialogue can be opened from anywhere in a page, so it cannot know what the surrounding heading structure is, and the page behind it already provides the single `<h1>`. Placing the title at level 2 keeps it directly beneath that `<h1>` wherever the dialogue is opened from.

Its appearance comes from the `fs-5` [Bootstrap font size utility class](https://getbootstrap.com/docs/5.3/utilities/text/#font-size), not from the element. Never change the heading element to make a title look bigger or smaller -- change the utility class.

:::info[Why `<h2>` and not `<h1>`?]

[Bootstrap's own documentation](https://getbootstrap.com/docs/5.3/components/modal/) uses an `<h1>` for the modal title, on the basis that a dialogue is its own document context. Moodle uses an `<h2>` instead because the page behind the dialogue already has an `<h1>`, and accessibility auditing tools commonly report a second `<h1>` as a failure.

:::

### Headings within a dialogue {/* #headings-within-a-dialogue */}

Because the title is an `<h2>`, the first heading level available to you inside the body or footer of a dialogue is `<h3>`:

```mustache title="mod/example/templates/my_modal.mustache"
{{< core/modal }}
{{$title}}{{#str}} pluginname, mod_example {{/str}}{{/title}}
{{$body}}
<h3>{{#str}} settings, mod_example {{/str}}</h3>
{{! ... }}
<h4>{{#str}} advancedsettings, mod_example {{/str}}</h4>
{{! ... }}
{{/body}}
{{/ core/modal }}
```

Skipping a level -- going straight from the `<h2>` title to an `<h4>`, for example -- breaks the heading hierarchy that assistive technology users rely on to navigate the dialogue. See the [heading requirements](/general/development/process/peer-review/accessibility-checklist#page-headers-and-title) in the accessibility peer review checklist.

This applies to content that is rendered *into* a dialogue as much as to the dialogue's own template. If a language string, filter, or renderer emits headings, and that output can be displayed in a dialogue, its heading levels must fit beneath the `<h2>` title too. The `modulename_help` strings shown by the activity chooser are one example: their Markdown headings start at `####` (`<h4>`) so that they nest correctly under the activity name.

:::tip[Sizing nested headings]

If a nested heading needs to look smaller than its level implies, apply an `fs-*` utility class, or set the size in your theme's SCSS. Choose the heading element for its meaning and the class for its appearance.

:::

### Overriding the header {/* #overriding-the-header */}

The `core/modal` template exposes a `header` block, which replaces the whole `modal-header` region including the title element. If you override it, you must render your own heading and it must keep the same level, id, and `modal-title` class, otherwise the dialogue loses the accessible name that `aria-labelledby` points at:

```mustache title="Overriding the header block"
{{< core/modal }}
{{$header}}
<h2 id="{{uniqid}}-modal-title" class="modal-title fs-5 text-truncate" data-region="title">
{{#str}} pluginname, mod_example {{/str}}
</h2>
{{! Any additional header content. }}
{{/header}}
{{/ core/modal }}
```

In most cases you should override the `title` block instead, and leave the heading itself to `core/modal`.

:::note[Dialogues that are not built with `core/modal`]

Some dialogue-like components render their own `modal-header` markup rather than extending `core/modal` -- `tool_usertours` tour steps are one example in core. These follow the same rule: the title is an `<h2 class="modal-title fs-5">`, and content headings start at `<h3>`.

:::

### Testing the heading structure {/* #testing-the-heading-structure */}

The `best-practice` axe ruleset checks heading order, so an `@accessibility` Behat scenario is the simplest way to guard the structure of a dialogue:

```gherkin
@accessibility
Scenario: The example dialogue has a valid heading structure
Given I open the example dialogue
Then the "Example dialogue" "dialogue" should meet accessibility standards with "best-practice" extra tests
```

Assert on the semantics rather than the presentation. `"h2.modal-title" "css_element"` is a stable assertion; including the `fs-5` utility class in the selector is not, because the class that sets the title's size may change.

See [Accessibility testing](/general/development/policies/accessibility/testing) for more on writing accessibility tests.

:::note[Earlier releases]

Before [MDL-75699](https://tracker.moodle.org/browse/MDL-75699) the dialogue title was an `<h5>`, and headings inside a dialogue were expected to nest beneath that. If you are writing code that must also run on releases from before that fix, be aware that the same markup produces a different heading hierarchy there.

:::

## Creating a custom modal type {/* #creating-a-custom-modal-type */}

In some situations it is desirable to write a brand new modal.
Expand Down
88 changes: 88 additions & 0 deletions versioned_docs/version-4.5/guides/javascript/modal/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,94 @@ export const init = async () => {
</TabItem>
</Tabs>

## Heading structure {/* #heading-structure */}

The `core/modal` template renders the dialogue title as a level 2 heading:

```mustache title="The title element in core/modal"
<h2 id="{{uniqid}}-modal-title" class="modal-title h5" data-region="title">Test title</h2>
```

The level is deliberately fixed at `<h2>`. A dialogue can be opened from anywhere in a page, so it cannot know what the surrounding heading structure is, and the page behind it already provides the single `<h1>`. Placing the title at level 2 keeps it directly beneath that `<h1>` wherever the dialogue is opened from.

Its appearance comes from the `h5` [Bootstrap typography utility class](https://getbootstrap.com/docs/4.6/content/typography/), not from the element. Never change the heading element to make a title look bigger or smaller -- change the utility class.

:::info[Why `<h2>` and not `<h1>`?]

[Bootstrap 4's own example](https://getbootstrap.com/docs/4.6/components/modal/) uses an `<h5>` for the modal title, which is where Moodle's original markup came from -- it picks the element for its size rather than for its place in the document. Bootstrap 5 corrected this to an `<h1>`, on the basis that a dialogue is its own document context. Moodle uses an `<h2>` instead, because the page behind the dialogue already has an `<h1>`, and accessibility auditing tools commonly report a second `<h1>` as a failure.

:::

### Headings within a dialogue {/* #headings-within-a-dialogue */}

Because the title is an `<h2>`, the first heading level available to you inside the body or footer of a dialogue is `<h3>`:

```mustache title="mod/example/templates/my_modal.mustache"
{{< core/modal }}
{{$title}}{{#str}} pluginname, mod_example {{/str}}{{/title}}
{{$body}}
<h3>{{#str}} settings, mod_example {{/str}}</h3>
{{! ... }}
<h4>{{#str}} advancedsettings, mod_example {{/str}}</h4>
{{! ... }}
{{/body}}
{{/ core/modal }}
```

Skipping a level -- going straight from the `<h2>` title to an `<h4>`, for example -- breaks the heading hierarchy that assistive technology users rely on to navigate the dialogue. See the [heading requirements](/general/development/process/peer-review/accessibility-checklist#page-headers-and-title) in the accessibility peer review checklist.

This applies to content that is rendered *into* a dialogue as much as to the dialogue's own template. If a language string, filter, or renderer emits headings, and that output can be displayed in a dialogue, its heading levels must fit beneath the `<h2>` title too.

:::tip[Sizing nested headings]

If a nested heading needs to look smaller than its level implies, apply a heading utility class such as `h6`, or set the size in your theme's SCSS. Choose the heading element for its meaning and the class for its appearance.

:::

### Overriding the header {/* #overriding-the-header */}

The `core/modal` template exposes a `header` block, which replaces the whole `modal-header` region including the title element. If you override it, you must render your own heading and it must keep the same level, id, and `modal-title` class, otherwise the dialogue loses the accessible name that `aria-labelledby` points at:

```mustache title="Overriding the header block"
{{< core/modal }}
{{$header}}
<h2 id="{{uniqid}}-modal-title" class="modal-title h5" data-region="title">
{{#str}} pluginname, mod_example {{/str}}
</h2>
{{! Any additional header content. }}
{{/header}}
{{/ core/modal }}
```

In most cases you should override the `title` block instead, and leave the heading itself to `core/modal`.

:::note[Dialogues that are not built with `core/modal`]

Some dialogue-like components render their own `modal-header` markup rather than extending `core/modal` -- `tool_usertours` tour steps are one example in core. These follow the same rule: the title is an `<h2 class="modal-title h5">`, and content headings start at `<h3>`.

:::

### Testing the heading structure {/* #testing-the-heading-structure */}

The `best-practice` axe ruleset checks heading order, so an `@accessibility` Behat scenario is the simplest way to guard the structure of a dialogue:

```gherkin
@accessibility
Scenario: The example dialogue has a valid heading structure
Given I open the example dialogue
Then the "Example dialogue" "dialogue" should meet accessibility standards with "best-practice" extra tests
```

Assert on the semantics rather than the presentation. `"h2.modal-title" "css_element"` is a stable assertion; including the `h5` utility class in the selector is not, because the class that sets the title's size may change.

See [Accessibility testing](/general/development/policies/accessibility/testing) for more on writing accessibility tests.

:::note[Earlier releases]

Before [MDL-75699](https://tracker.moodle.org/browse/MDL-75699) the dialogue title was an `<h5>`, and headings inside a dialogue were expected to nest beneath that. If you are writing code that must also run on releases from before that fix, be aware that the same markup produces a different heading hierarchy there.

:::

## Creating a custom modal type {/* #creating-a-custom-modal-type */}

In some situations it is desirable to write a brand new modal.
Expand Down
88 changes: 88 additions & 0 deletions versioned_docs/version-5.1/guides/javascript/modal/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,94 @@ export const init = async () => {
</TabItem>
</Tabs>

## Heading structure {/* #heading-structure */}

The `core/modal` template renders the dialogue title as a level 2 heading:

```mustache title="The title element in core/modal"
<h2 id="{{uniqid}}-modal-title" class="modal-title fs-5 text-truncate" data-region="title">Test title</h2>
```

The level is deliberately fixed at `<h2>`. A dialogue can be opened from anywhere in a page, so it cannot know what the surrounding heading structure is, and the page behind it already provides the single `<h1>`. Placing the title at level 2 keeps it directly beneath that `<h1>` wherever the dialogue is opened from.

Its appearance comes from the `fs-5` [Bootstrap font size utility class](https://getbootstrap.com/docs/5.3/utilities/text/#font-size), not from the element. Never change the heading element to make a title look bigger or smaller -- change the utility class.

:::info[Why `<h2>` and not `<h1>`?]

[Bootstrap's own documentation](https://getbootstrap.com/docs/5.3/components/modal/) uses an `<h1>` for the modal title, on the basis that a dialogue is its own document context. Moodle uses an `<h2>` instead because the page behind the dialogue already has an `<h1>`, and accessibility auditing tools commonly report a second `<h1>` as a failure.

:::

### Headings within a dialogue {/* #headings-within-a-dialogue */}

Because the title is an `<h2>`, the first heading level available to you inside the body or footer of a dialogue is `<h3>`:

```mustache title="mod/example/templates/my_modal.mustache"
{{< core/modal }}
{{$title}}{{#str}} pluginname, mod_example {{/str}}{{/title}}
{{$body}}
<h3>{{#str}} settings, mod_example {{/str}}</h3>
{{! ... }}
<h4>{{#str}} advancedsettings, mod_example {{/str}}</h4>
{{! ... }}
{{/body}}
{{/ core/modal }}
```

Skipping a level -- going straight from the `<h2>` title to an `<h4>`, for example -- breaks the heading hierarchy that assistive technology users rely on to navigate the dialogue. See the [heading requirements](/general/development/process/peer-review/accessibility-checklist#page-headers-and-title) in the accessibility peer review checklist.

This applies to content that is rendered *into* a dialogue as much as to the dialogue's own template. If a language string, filter, or renderer emits headings, and that output can be displayed in a dialogue, its heading levels must fit beneath the `<h2>` title too.

:::tip[Sizing nested headings]

If a nested heading needs to look smaller than its level implies, apply an `fs-*` utility class, or set the size in your theme's SCSS. Choose the heading element for its meaning and the class for its appearance.

:::

### Overriding the header {/* #overriding-the-header */}

The `core/modal` template exposes a `header` block, which replaces the whole `modal-header` region including the title element. If you override it, you must render your own heading and it must keep the same level, id, and `modal-title` class, otherwise the dialogue loses the accessible name that `aria-labelledby` points at:

```mustache title="Overriding the header block"
{{< core/modal }}
{{$header}}
<h2 id="{{uniqid}}-modal-title" class="modal-title fs-5 text-truncate" data-region="title">
{{#str}} pluginname, mod_example {{/str}}
</h2>
{{! Any additional header content. }}
{{/header}}
{{/ core/modal }}
```

In most cases you should override the `title` block instead, and leave the heading itself to `core/modal`.

:::note[Dialogues that are not built with `core/modal`]

Some dialogue-like components render their own `modal-header` markup rather than extending `core/modal` -- `tool_usertours` tour steps are one example in core. These follow the same rule: the title is an `<h2 class="modal-title fs-5">`, and content headings start at `<h3>`.

:::

### Testing the heading structure {/* #testing-the-heading-structure */}

The `best-practice` axe ruleset checks heading order, so an `@accessibility` Behat scenario is the simplest way to guard the structure of a dialogue:

```gherkin
@accessibility
Scenario: The example dialogue has a valid heading structure
Given I open the example dialogue
Then the "Example dialogue" "dialogue" should meet accessibility standards with "best-practice" extra tests
```

Assert on the semantics rather than the presentation. `"h2.modal-title" "css_element"` is a stable assertion; including the `fs-5` utility class in the selector is not, because the class that sets the title's size may change.

See [Accessibility testing](/general/development/policies/accessibility/testing) for more on writing accessibility tests.

:::note[Earlier releases]

Before [MDL-75699](https://tracker.moodle.org/browse/MDL-75699) the dialogue title was an `<h5>`, and headings inside a dialogue were expected to nest beneath that. If you are writing code that must also run on releases from before that fix, be aware that the same markup produces a different heading hierarchy there.

:::

## Creating a custom modal type {/* #creating-a-custom-modal-type */}

In some situations it is desirable to write a brand new modal.
Expand Down
Loading
Loading