Let a card's title take the heading level the page needs

<x-card> always titled itself with an <h3>, so a card straight under a
page's <h1> skipped a level, which SealShare's download page did with
no way round it from the application. `heading` takes h2 to h6, or div
or p for a title that is not a heading, as <x-pane> and <x-app-bar>
already do; h3 stays the default. The skill also says a card holds one
subject, as M3 does (plan step 46).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Andreas Reinhold / reini
2026-09-15 14:09:06 +02:00
co-authored by Claude Opus 5
parent d557dd9b49
commit ff5349759b
3 changed files with 14 additions and 3 deletions
@@ -476,7 +476,7 @@ The `illustration` slot draws the application's own artwork in place of the shap
### `<x-card>`
`variant`: `filled` (default, surface-container-highest), `elevated`, `outlined`; medium corner. Props `title`, `subtitle`, `separator`; slots `figure` (full-bleed media), `menu` (top-end), `actions` (end-aligned). Do not pass `bg-*`; use `variant`.
`variant`: `filled` (default, surface-container-highest), `elevated`, `outlined`; medium corner. Props `title`, `subtitle`, `separator`, `heading` (the title's element, `h3` by default: pass `h2` for a card straight under the page's `h1`); slots `figure` (full-bleed media), `menu` (top-end), `actions` (end-aligned). A card holds content and actions about one subject (M3); a table of many rows or a group of settings is a headed section, not a card. Do not pass `bg-*`; use `variant`.
A card or list item that opens something is a **row**: `data-md-list-row` on it and `data-md-list-open` on its one opener (the title link or a button). A press anywhere else on the row reaches the opener; its other controls keep their own presses. Never wrap a card in `<a>` or use a stretched link. A row answers with the state layer and one step of elevation; its corner does not move.
+6 -2
View File
@@ -5,7 +5,9 @@
`elevated` (surface-container-low at elevation 1) and `outlined` (surface, an outline-variant
edge); all with a medium corner. maryUI's props keep their meaning: `title`, `subtitle`,
`separator` (a divider under the header), and the `menu` (top-end, beside the title),
`figure` (full-bleed media on top) and `actions` (end-aligned, under the content) slots.
`figure` (full-bleed media on top) and `actions` (end-aligned, under the content) slots. The title
is an `<h3>` unless `heading` names the level the page's outline needs (`h2` … `h6`, or `div`/`p`
for a title that is not a heading), so a card straight under a page's `<h1>` skips no level.
M3 draws two kinds of card a person can press, and they differ in the keyboard: on a
**non-actionable card with actionable elements** Tab moves through each control inside before
@@ -51,12 +53,14 @@
'title' => null,
'subtitle' => null,
'variant' => 'filled',
'heading' => 'h3',
'separator' => false,
'actionable' => false,
])
@php
$variant = in_array($variant, ['filled', 'elevated', 'outlined'], true) ? $variant : 'filled';
$heading = in_array($heading, ['h2', 'h3', 'h4', 'h5', 'h6', 'div', 'p'], true) ? $heading : 'h3';
$role = $actionable ? ($attributes->get('role') ?: 'button') : null;
$isRow = $actionable || $attributes->has('data-md-list-row');
@@ -87,7 +91,7 @@
<div data-md-card-header-row>
<div data-md-card-heading>
@if ($title)
<h3 data-md-card-title>{{ $title }}</h3>
<{{ $heading }} data-md-card-title>{{ $title }}</{{ $heading }}>
@endif
@if ($subtitle)
+7
View File
@@ -93,3 +93,10 @@ it('rings the row from the card itself when actionable, or from a has() when its
->and($css->declarations('[data-md-card][data-md-list-row] [data-md-list-open]:focus-visible'))
->toBe(['outline' => 'none']);
});
it('titles the card at the heading level the page\'s outline needs', function () {
expect((string) $this->blade('<x-card title="Shared files" heading="h2">Body</x-card>'))
->toContain('<h2 data-md-card-title>Shared files</h2>')
->and((string) $this->blade('<x-card title="Shared files" heading="span">Body</x-card>'))
->toContain('<h3 data-md-card-title>Shared files</h3>');
});