GUILD OF GLEKS UIv21.6.1

gog-card

Card 21.6.1

A surface for one self-contained thing — a product tile, a summary, a search result. What it adds over a .card class of your own is what a class cannot do: it takes its accessible name from the projected heading, it folds in loading and disabled, and gogCardLink makes the whole surface activate a real link — your <a>, with your routerLink still on it.

Overview

Import the component and the slots you use. The heading is what names the card — a card without one is an unnamed group, so it deliberately gets no role at all.

typescript
import {
  CardComponent,
  GogCardFooterDirective,
  GogCardHeaderDirective,
  GogCardLinkDirective,
  GogCardMediaDirective,
} from '@guildofgleks/ui';

@Component({
  // ...
  imports: [CardComponent, GogCardHeaderDirective],
})

Quarterly summary

The heading names the card: it takes that element's id and points its own aria-labelledby at it. Without a header a card is an unnamed group, so it gets neither.

<gog-card>
  <h3 gogCardHeader>Quarterly summary</h3>
  <p>
    The heading names the card: it takes that element's id and points its own
    <code>aria-labelledby</code> at it. Without a header a card is an unnamed group, so it gets
    neither.
  </p>
</gog-card>

Card or panel?

Both paint a surface, and the library ships them as two components because they answer different questions. The quickest test is about links:

  • Is the whole surface a link? Then it is a card, with a gogCardLink. A gog-panel is never a link — there is no gogPanelLink, because a region that is a link cannot hold the controls a panel exists to hold.
  • Otherwise: a card is one self-contained thing, usually one of many in a grid, and announces as role="group" so twenty of them do not bury the landmark list. A panel is a titled region of a page — a real landmark, one of a handful, and it can collapse.

Examples

Variants and sizes

The same three variants as gog-panel, from the shared GogSurfaceVariant type — but outlined is the default here, because a grid of many cards wants a border rather than twenty shadows. size drives padding and the row gap on the same five-tier scale as every control.

outlined

Outlined is the default — a border and no shadow, which is what a grid of many wants.

elevated

Outlined is the default — a border and no shadow, which is what a grid of many wants.

filled

Outlined is the default — a border and no shadow, which is what a grid of many wants.

xsm

Same copy, every tier.

sm

Same copy, every tier.

md

Same copy, every tier.

lg

Same copy, every tier.

slg

Same copy, every tier.

<div class="variants">
  @for (variant of variants; track variant) {
    <gog-card [variant]="variant">
      <h4 gogCardHeader>{{ variant }}</h4>
      <p>Outlined is the default — a border and no shadow, which is what a grid of many wants.</p>
    </gog-card>
  }
</div>

<div class="sizes">
  @for (size of sizes; track size) {
    <gog-card [size]="size">
      <h4 gogCardHeader>{{ size }}</h4>
      <p>Same copy, every tier.</p>
    </gog-card>
  }
</div>

Interactive — the whole surface activates one real link

There is no interactive input and no gogClick output. A card becomes interactive by containing a gogCardLink, which stretches that link's hit area over the whole surface. The link stays yours, so routerLink, href, target, middle-click, "open in new tab" and Enter all behave normally, and the focus ring is drawn around the card.

Anything else focusable in the card still receives its own clicks — press the footer button and watch the tally, then click anywhere else on the card and follow the link instead.

Ada Lovelace

Mathematician

Alan Turing

Logician

Footer clicks that reached their own button: 0

<div class="grid">
  @for (person of people; track person.id) {
    <gog-card>
      <h4 gogCardHeader>
        <a gogCardLink routerLink="/components/card" [fragment]="person.id">{{ person.name }}</a>
      </h4>
      <p>{{ person.role }}</p>
      <div gogCardFooter>
        <gog-tag variant="info" size="xsm">Profile</gog-tag>
        <gog-button size="xsm" variant="ghost" (gogClick)="shortlist()">Shortlist</gog-button>
      </div>
    </gog-card>
  }
</div>

<p class="tally">
  Footer clicks that reached their own button: <strong>{{ footerClicks() }}</strong>
</p>

Two costs, inherent to the pattern rather than to this implementation: text in the card cannot be selected by dragging, and a second link is reachable by keyboard but not by clicking the surface around it. If either matters, do not stretch the link — put an ordinary link in the heading and leave the surface inert.

gogCardLink applies to <a> and <button> only. On a <div> it does nothing, on purpose: a div with a click handler is the thing this directive exists to stop you writing.

Media

gogCardMedia runs full-bleed to the card's edges and rounds into its top corners. The rendered order of the slots is fixed by the component — media, heading, body, footer — and does not follow the order you write them in: the two cards below are authored in opposite orders and render identically, which is what keeps a grid aligned when one card happens to be written differently from its neighbours.

Media written first

Full-bleed to the card's edges, rounded into its top corners.

Media written last

Authored after the heading and the body — and rendered identically to the card beside it. The component fixes the slot order, so the write order never leaks into the layout.

<div class="grid">
  <gog-card>
    <img [src]="mediaSrc" gogCardMedia alt="" height="120" />
    <h4 gogCardHeader>Media written first</h4>
    <p>Full-bleed to the card's edges, rounded into its top corners.</p>
  </gog-card>

  <gog-card>
    <h4 gogCardHeader>Media written last</h4>
    <p>
      Authored after the heading and the body — and rendered identically to the card beside it. The
      component fixes the slot order, so the write order never leaks into the layout.
    </p>
    <img [src]="mediaSrc" gogCardMedia alt="" height="120" />
  </gog-card>
</div>

The bleed is inline-size: calc(100% + 2 * padding-x) plus a negative inline margin, so do not give the media a width of your own — a width: 100% overrides that calculation and the picture ends one padding short of the card's right edge while still starting flush at its left.

Loading and disabled

loading swaps the content for a title bar plus skeletonLines body lines and marks the host aria-busy. disabled dims the card, sets aria-disabled, and takes its link out of the tab order. Both make the card's link non-clickable.

loading is the first-paint treatment. For a refresh of a card that already has content, project a gog-spinner-overlay instead — replacing real content with placeholders is a worse answer once the reader has something to look at.

Quarterly summary

Revenue is up 12% on the quarter, driven mostly by renewals rather than new business.

Archived report

Tab through the pair: only the enabled card's link stops.

<div class="grid">
  <div class="cell">
    <gog-button size="xsm" variant="outline" (gogClick)="toggleLoading()">
      {{ loading() ? 'Finish loading' : 'Load again' }}
    </gog-button>
    <gog-card [loading]="loading()" [skeletonLines]="3">
      <h4 gogCardHeader>Quarterly summary</h4>
      <p>Revenue is up 12% on the quarter, driven mostly by renewals rather than new business.</p>
    </gog-card>
  </div>

  <div class="cell">
    <gog-button size="xsm" variant="outline" (gogClick)="toggleDisabled()">
      {{ disabled() ? 'Enable' : 'Disable' }}
    </gog-button>
    <gog-card [disabled]="disabled()">
      <h4 gogCardHeader>
        <a gogCardLink routerLink="/components/card">Archived report</a>
      </h4>
      <p>Tab through the pair: only the enabled card's link stops.</p>
    </gog-card>
  </div>
</div>

Theming

Every value comes from a --gog-card-* token, and the instance tier (--gog-card-bg, --gog-card-border-color, --gog-card-shadow, --gog-card-padding-y, --gog-card-padding-x, --gog-card-gap) is left undeclared on purpose, so setting one on an element beats the variant and size classes without a specificity fight.

Overridden per instance

Three instance tokens from one class on the element. No ::ng-deep.

<gog-card class="themed">
  <h4 gogCardHeader>Overridden per instance</h4>
  <p>Three instance tokens from one class on the element. No <code>::ng-deep</code>.</p>
</gog-card>

API Reference

Inputs

NameTypeDefaultDescription
variant21.6.1GogSurfaceVariant'outlined''outlined' | 'elevated' | 'filled'. Outlined is the default — a border and no shadow, which is what a grid of many wants.
size21.6.1GogSize'md'Drives padding and the gap between the card’s rows, on the five-tier scale.
disabled21.6.1booleanfalseDims the card, sets aria-disabled, and takes the card link out of the tab order. A bare attribute works.
loading21.6.1booleanfalseReplaces the content with a title bar plus skeletonLines text lines and sets aria-busy. The first-paint treatment, not a refresh.
skeletonLines21.6.1number2How many body lines the loading placeholder draws.

Slots

NameDescription
gogCardHeader21.6.1Your own heading. The card takes its id (minting one if needed), points aria-labelledby at it, and announces as role="group". Without it the card gets neither.
gogCardMedia21.6.1Runs full-bleed to the card’s edges and rounds into its top corners. Rendered above the heading whatever order you write it in.
gogCardFooter21.6.1Rendered last, below the body. Its controls keep their own clicks.
gogCardLink21.6.1On your own <a> or <button>: stretches that link’s hit area over the whole card. Ignored on any other element, deliberately.

Style tokens

TokenDescription
--gog-card-bg / -border-color / -shadow / -padding-y / -padding-x / -gapThe instance tier — left undeclared by the library on purpose, so setting one on an element beats the variant and size classes without a specificity fight.
--gog-card-outlined-bg / -outlined-border-color / -outlined-shadowThe outlined variant (the default): a border, no shadow.
--gog-card-elevated-bg / -elevated-border-color / -elevated-shadowThe elevated variant: the shared surface shadow, no border.
--gog-card-filled-bg / -filled-border-color / -filled-shadowThe filled variant: a tint, neither border nor shadow.
--gog-card-color / --gog-card-font-family / --gog-card-radiusBody text color, font stack and corner radius.
--gog-card-heading-color / -heading-font-family / -heading-font-size / -heading-font-weight / -heading-line-heightThe projected gogCardHeader. The visual size comes from these regardless of which heading level you use.
--gog-card-footer-border-color / -footer-gap / -footer-padding-topThe projected gogCardFooter row and the rule above it.
--gog-card-hover-border-color / --gog-card-hover-shadowHover treatment, which only appears on a card holding a gogCardLink.
--gog-card-focus-ring / -focus-ring-width / -focus-ring-offsetThe ring drawn around the whole card when its stretched link takes focus.
--gog-card-xsm-padding-y … --gog-card-slg-padding-y (and -padding-x, -gap)Padding and row gap per size tier, xsm through slg.
--gog-card-disabled-opacity / --gog-card-transition-durationDisabled dimming, and the hover/focus transition.