ComponentsLumos 0.0.1UpdatedOpen component

Card

A self-contained block (eyebrow, heading, text and a button) in three arrangements.

Card takes its content as props rather than slots, which is what makes a grid of them consistent: every card gets the same heading size, the same button style and the same internal spacing, whatever is in it.

astro
<Grid xsmall={1} medium={3}>
  <Card heading="Default" text="Text only." buttonLabel="Read more" buttonHref="#" />
  <Card variant="cover" image={cover} heading="Cover" buttonLabel="Read more" buttonHref="#" />
  <Card variant="stacked" image={cover} heading="Stacked" buttonLabel="Read more" buttonHref="#" />
</Grid>

The three variants in a three-column grid.

Props

Content

PropTypeDefaultWhat it does
renderbooleantrueSet to false to skip this component and everything inside it.
eyebrowstringNoneA small line above the heading. Nothing renders without one.
headingstringNoneThe card's title.
headingTag"h1"–"h6""h3"The heading level, for the page outline. Every card heading is the same size whatever the level.
textstringNoneBody copy under the heading.
buttonLabelstringNoneThe closing button's label. No button renders without one.
buttonHrefstringNoneWhere the button leads. Left out, it renders as a plain button.

Appearance

PropTypeDefaultWhat it does
variant"default" | "cover" | "stacked""default"default is text only. cover fills the card with the image and puts the text over it. stacked puts the image above the text, bleeding to the edges.
imageImageMetadata | stringNoneAn imported asset, a remote URL, or a path to a file in public/. Only on cover and stacked.
imageAltstringNoneLeave unset on a decorative image: it renders alt="".
theme"inherit" | "light" | "dark" | "brand" | "invert""inherit"Colors the card carries with it, whatever section it sits in. Not available on stacked, which has no surface of its own.

The button matches the variant

Each variant closes with the button style that suits it (primary on a cover card, secondary on a default one, a link on a stacked one), so a mixed set still reads as one system. You do not choose it.

Buttons line up across a row

In a grid of cards with different amounts of text, every button still sits on the card's floor. That is handled by the framework, not by you setting a height.

When props are not enough

Card is deliberately narrow. When you need something it does not do (a price, a rating, a second button), build your own component rather than adding props to this one. Use ContentWrapper and the text components inside it and you will still be inside the system.

Lists of cards

When cards are a list of things, say so in the markup: a <ul> around them and role="listitem" on each. A screen reader then announces how many there are before reading them.

astro
<Grid xsmall={1} medium={3}>
  <ul class="display-contents">
    {posts.map((post) => (
      <Card role="listitem" heading={post.title} text={post.excerpt} />
    ))}
  </ul>
</Grid>