A card that is one link: a press anywhere opens it, and the switch answers on its own.
21.4 °C
A box for one thing on a dashboard: a title, a subtitle, an icon and actions above, anything in the body, and a footer that stays at the bottom. The whole card can be one link.
@pacit/components/cardpct-card, [pctCardTitle], [pctCardSubtitle], a[pctCardLink], [pctCardControl]No APG pattern applies, and none is invented — the reasoning is under Accessibility ↓
Utility room
21.4 °C
import { Component } from '@angular/core';
import { PctCard, PctCardSubtitle, PctCardTitle } from '@pacit/components/card';
/** A card: a title, a subtitle, a body and a footer that stays down. */
@Component({
selector: 'demo-card',
imports: [PctCard, PctCardTitle, PctCardSubtitle],
template: `
<pct-card style="inline-size: 240px">
<h3 pctCardTitle>Heat pump</h3>
<p pctCardSubtitle>Utility room</p>
<p style="margin: 0; font-size: 24px">21.4 °C</p>
<span pctCardFooter>Updated a moment ago</span>
</pct-card>
`,
})
export class CardDemo {}card.demo.ts — the same file runs here and is highlighted under Code
One import per component, and the shortest possible use — the package itself is installed once, on the start page.
import { PctCard, PctCardLink, PctCardTitle, PctCardSubtitle } from '@pacit/components/card';| Exported | What it is |
|---|---|
PctCardLinkcomponentunreleaseda[pctCardLink] | The anchor that makes the whole card one link: the consumer's <a> inside the title, with its own href or routerLink, whose box is stretched over the card by a pseudo-element. |
PctCardControldirectiveunreleased[pctCardControl] | A control that answers on its own inside a card that is one link: it stands over the link's box, so a press on it is its own and not the link's. The header's actions and the footer's actions stand there already; this is for a control written in the body, or for a passage whose text must stay selectable, which then leaves the link. |
PctCardTitlecomponentunreleased[pctCardTitle] | The card's title: the consumer's heading, at the level the page's outline needs, with the card's type on it and the browser's margins taken off. |
PctCardSubtitlecomponentunreleased[pctCardSubtitle] | The line under the card's title: where the thing stands, or what kind it is. |
PctCardcomponentunreleasedpct-card | A card: a header of a title, a subtitle, an icon and actions, a body of anything, and a footer that stays at the bottom when the cards of a row stand at one height. |
<pct-card>
<h3 pctCardTitle><a pctCardLink routerLink="/devices/heat-pump">Heat pump</a></h3>
<p pctCardSubtitle>Utility room</p>
<p>21.4 °C</p>
<span pctCardFooter>Updated a moment ago</span>
</pct-card>Each example is the component itself, not a screenshot. Open the code under any of them.
21.4 °C
Read from the source at build time — the types, the defaults and the lines of documentation are the library's own.
unreleased marks 8 of them: on main, and not in @pacit/components@0.2.0 — the next release names the version.
A card: a header of a title, a subtitle, an icon and actions, a body of anything, and a footer that stays at the bottom when the cards of a row stand at one height.
| Name | Type | What it does |
|---|---|---|
loadingunreleaseddefault false | boolean | Whether the card's content is on its way: the body gives way to the skeleton, and the card is aria-busy until it arrives. The header and the footer stay. |
errorunreleaseddefault null | string | null | A message saying why the card has nothing to show, drawn in place of the body with the danger icon; null or '' for none. loading wins while both are set: the answer is not in yet. |
mutedunreleaseddefault false | boolean | Whether the thing the card stands for is out of reach — a device that does not answer. The card stands on the skin's disabled surface and keeps its controls working; say why in the card's own words, because colours are the eye's alone. |
(click), (focus), the form's submit — are the platform's, and two-way models above carry the rest. The hooks a stylesheet or a test can hold. Stable by contract — the parts inventory gate fails the build when one disappears.
| Attribute | Bound to | Note |
|---|---|---|
class | pct-card | |
aria-busy | loading() ? "true" : null | |
data-pct-state | state() | |
data-pct-muted | muted() ? "" : null | |
data-pct-link | link() ? "" : null |
Two ways in, both stable. No class names to depend on.
In a test, PctCardHarness, PctCardTitleHarness, PctCardSubtitleHarness, PctCardLinkHarness, PctCardControlHarness from @pacit/components/testing read a part by the name below and a state by its attribute — the harness gate holds the names to the package.
| Part | Selector | What it is |
|---|---|---|
actions | [data-pct-part="actions"] | the region of the header's controls, at its end |
body | [data-pct-part="body"] | the region of the body, or of the skeleton while loading |
footer | [data-pct-part="footer"] | the row at the bottom of the card |
footer-actions | [data-pct-part="footer-actions"] | the region of the footer's controls |
header | [data-pct-part="header"] | the row of the icon, the heading and the actions |
heading | [data-pct-part="heading"] | the column of the title and the subtitle |
icon | [data-pct-part="icon"] | the region of the consumer's icon, at the header's start |
message | [data-pct-part="message"] | the error message drawn in place of the body |
message-icon | [data-pct-part="message-icon"] | the danger icon beside the message |
note | [data-pct-part="note"] | the region of the footer's text |
Every value the component reads, with the light and the dark default. Override a token at any scope and it cascades like any custom property.
| Token | Styles |
|---|---|
--pct-card-bg{pct.surface}#ffffff · #0f172a | Background of the card. |
--pct-card-bg-muted{pct.surface-disabled}#f1f5f9 · #1e293b | Background of a muted card. |
--pct-card-border{pct.border}#e2e8f0 · #334155 | Edge colour of the card. |
--pct-card-border-hover{pct.border-strong}#64748b | Edge colour of a card that is one link, under the pointer. |
--pct-card-fg{pct.text}#0f172a · #ffffff | Text colour of the card. |
--pct-card-fg-muted{pct.text-muted}#334155 · #e2e8f0 | Text colour of a muted card. |
--pct-card-footer-fg{pct.text-muted}#334155 · #e2e8f0 | Text colour of the card's footer. |
--pct-card-footer-font-size{pct.font.size.sm}13px | Font size of the card's footer. |
--pct-card-gap{pct.space.4}12px | Gap between the card's header, body and footer. |
--pct-card-header-gap{pct.space.3}8px | Gap between the icon, the heading and the actions in the header. |
--pct-card-icon-color-danger{pct.danger}#b91c1c · #f87171 | Colour of the icon beside the card's error message. |
--pct-card-message-bg-danger{pct.danger-100}#fee2e2 · #991b1b | Background of the card's error message. |
--pct-card-message-fg-danger{pct.on-danger-100}#991b1b · #fee2e2 | Text colour of the card's error message. |
--pct-card-padding-x{pct.space.5}16px | Inline padding inside the card's edge. |
--pct-card-padding-y{pct.space.5}16px | Block padding inside the card's edge. |
--pct-card-radius{pct.radius.md}8px | Corner radius of the card. |
--pct-card-subtitle-fg{pct.text-muted}#334155 · #e2e8f0 | Text colour of the card's subtitle. |
--pct-card-subtitle-font-size{pct.font.size.sm}13px | Font size of the card's subtitle. |
--pct-card-title-font-size{pct.font.size.lg}16px | Font size of the card's title. |
--pct-card-title-font-weight{pct.font.weight.medium}500 | Font weight of the card's title. |
A brand override in a few declarations. Scope it to a theme attribute, a page or one container — the component never notices.
[data-theme='brand'] {
--pct-card-border: #99f6e4;
--pct-card-border-hover: #0f766e;
--pct-card-radius: 16px;
}Utility room
21.4 °C
Pattern: none — the APG has no pattern for a card. The card writes no role; its heading is the consumer's, at the level the page's outline needs, and a card that is one link is the consumer's <a> in that heading. Named in the class JSDoc.
| key | effect | test |
|---|---|---|
Tab / Shift+Tab | moves to the card's link, then to its controls in order | apps/sandbox-e2e/src/card.spec.ts — "the keyboard meets the link, then the controls, and Enter follows the link" |
Enter | on the link, follows it | the same case; the following is the platform's |
There are no handlers to map: the link is an anchor and the controls are the consumer's, and each answers the keyboard on its own.
Every line is a test in three engines or a gate on every commit. The gaps are listed the same way, each with the requirement that will close it.
libs/components/card/src/card.tsapps/sandbox-e2e/src/card.spec.tsapps/sandbox-e2e/src/a11y.spec.ts — /card in SBX_ROUTES, the card that is one link and the three states includedapps/sandbox-e2e/src/visual.spec.ts — card-grid and card-grid-rtlforced-colors: active — no state carried by colour alonelibs/components/card/src/card.scss and card-link.scss — the edge CanvasText, a muted card GrayText, the error a box of its own, the link's ring Highlight. The reading: apps/sandbox-e2e/src/forced-colors.spec.tsprefers-reduced-motion — duration from a tokennot applicable — the card moves nothing; the skeleton it draws while loading follows its own card--pct-control-height-*deliberately not: a card is not a control, and it is sized by its content and the spacing scalelibs/tokens/src/component.card.json — the padding and the gaps stand on space.3, space.4 and space.5, which the compact scope re-pointsdir="rtl" screenshotlogical properties only, held by check-styles; the screenshot card-grid-rtl, where the actions move to the leftNG05xxapps/sandbox-e2e/src/hydration.spec.ts — /card in SBX_ROUTESlibs/components/parts.snapshot.md — the ten parts abovecontrast.policy.jsonlibs/tokens/src/contrast.policy.json — nine entries: the text, the subtitle, the footer, the muted text and the error message over their surfaces (AA error), the error icon (UI error), the edges (UI warn); libs/tokens/tokens.snapshot.mdPCT_TEXTSnot applicable — the component writes no word of its ownlibs/components/size.snapshot.md — the ./card rowdocs/acr/at/ — three readers, whole sandbox. One link, named by the title, and then the controls: Orca Heat pump link · heading 3., then Enabled · switch pressed. and Rename · button.; NVDA Heat pump, heading, with no role word for the link, then Enabled, switch, on and Rename, button. VoiceOver reached the card's switch alone in both walks of the day (the first, which lost /text, was not kept), Enabled on switch, and lost the page after it, so it has not read the linkapps/sandbox/src/app/views/card/ (the sandbox view). The published site: /components/card, its demos' source in the code tablibs/components/card/src/card.spec.ts; libs/components/mutation.snapshot.md — card.ts, measured by a narrow runRead at build time from the tracked snapshots, not typed here.
pctCardControl on a passage takes it out of the link.pctCardControl. Without it the control sits under the link's box and a press on it is the link's. A control is anything the consumer writes, so nothing can warn about one that was missed.pctCardControl writes two inline declarations. position: relative and z-index: 1 stand on the element whether or not the card holds a link, so they override a position of the consumer's own, and two raised controls stack in their order in the document.position: relative while it holds a link, so an absolutely positioned child of the card is placed against the card.Heat pump, heading, where Orca says link. The element is an <a href> in an <h3>, and Enter follows it; whether the stretched box changes what NVDA reads is for the next walk to measure against a plain link in a heading.role="alert" per card that failed. A failure the user is waiting for is the application's to say, with a toast or the announcer.