Card and Image

These low-level building blocks are useful when composing a custom section. Card supplies the same article used by the two collection components; Image supplies shared responsive and theme-aware image rendering.

svelte
<script lang="ts">
  import { Card, Image, type CardItem } from '@page-sections/svelte';
  import '@page-sections/svelte/styles.css';
  const item: CardItem = {
    id: 'guide',
    title: 'A first garden',
    href: '/guide',
    description: 'Start small.'
  };
</script>

<div class="ps-shell">
  <Card {item} variant="editorial" mediaAspect="4/3">
    {#snippet media(card)}
      <Image
        image={{
          src: '/images/garden.jpg',
          alt: `Cover for ${card.title}`,
          aspect: '4/3',
          width: 1200,
          height: 900
        }}
      />
    {/snippet}
    {#snippet children(card)}<small>From the garden library: {card.title}</small
      >{/snippet}
  </Card>
</div>

Card accepts tone="standard" (default) or "muted". CardGridSection and CarouselSection forward their cardTone option to this prop. The selected surface follows the theme; custom surface snippets retain control over their own paint.

Card props

Generic T extends CardItem; required item. Optional cardSize (m), variant (catalog), mediaAspect (3/2; also 4/3 or 1/1), mediaFit (contain for catalog, cover for editorial), reserveEyebrow (otherwise follows the item), textAlign (start/center), headingLevel (inherited, standalone h2), media(item), surface(item), children(item) and onready(parts). Children are editorial-only. Card forwards HTMLElement attributes, including class and event callbacks; card identity/accessibility attributes remain component-owned.

Complete CardItem model

FieldTypeDefault / behavior
idstring, requiredContent identity
titlestring, requiredInherited heading; full-card link for catalog, title link for editorial
itemKeystringOptional occurrence key; collection keys use itemKey then id
hrefstringOptional native link, passed through safeHref
eyebrowstringOptional text before title
descriptionstringOptional description paragraph
subtitlestringFallback when description is nullish; an empty description suppresses it
imageImageDataOptional default media
footerInlineTextEditorial footer
actionsContentAction[]Independent editorial actions

Catalog height is the selected media frame plus an independent text area. S/M reserve two title lines; L reserves three. Eyebrows add a separate line (hidden for S); reserveEyebrow keeps an empty line when aligning cards with missing labels. Collections compute this flag from the full list. Text padding is 12px on each side. Catalog does not render descriptions, children, footers or actions; use editorial for those. Editorial grows with content and fills its grid slot.

CardMediaAspect is 3/2, 4/3 or 1/1. CardMediaFit is contain (8px media inset, whole image) or cover (no inset, cropped image). The frame ratio and image fit are independent. Card media props override ImageData.aspect/objectFit inside the slot; standalone Image behavior is unchanged. Missing catalog media keeps its frame; editorial cards without media omit it.

onready?: (parts: CardElements<T>) => void | (() => void) supplies item, root, surface, media, copy and title elements after mount. Its returned cleanup runs when the item/callback changes or the card unmounts. Keyed moves and media-format changes preserve the same occurrence. The media element is the inner fitting rectangle; media-frame is the outer aspect-ratio box. Observe element resizing when fitting external geometry: onready is an attachment hook, not a resize notification. data-card-id contains itemKey ?? id; data-ps-card-part names each measured part. Custom catalog snippets must not introduce interactive descendants inside a linked card. These hooks support external enhancements without a rendering-engine dependency.

Image props and complete ImageData model

Required image: ImageData; optional eager: boolean defaults to false. No children or attribute-forwarding props.

ImageData fieldTypeDefault / behavior
srcstring, requiredFallback image URL
altstring, requiredAlternative text; empty for decorative images
srcDarkstringOptional dark source in picture
width, heightnumberOptional intrinsic dimensions forwarded to img
aspectImageAspectUnset/none: no forced ratio; 4/3, 16/9, 2/1 or square
objectFit'cover' or 'contain'cover
srcset, sizesstringOptional responsive attributes on the fallback img

ImageAspect is 'none', '4/3', '16/9', '2/1', 'square'. The picture fills its available width; img height is auto, uses the selected ratio and --ps-radius. Loading is lazy unless eager; decoding is async. HeroSection uses eager mode, while cards and ImageSection use the default.

An ancestor PageShell's explicit mode determines whether srcDark is selected. Without one, the source uses prefers-color-scheme: dark. A data-ps-theme attribute alone changes CSS but does not provide PageShell's Svelte image-mode context. Explicit light mode suppresses the dark source.

Accessibility and SSR

Use meaningful alt text, avoid repeating a decorative card cover in speech, and supply width/height to reduce shifts. Native picture selection and loading work in server-rendered HTML. The library does not resize, upload or generate responsive assets.

Related: ImageSection, CardGridSection, Themes and Styling.