Cards, icons, images and badges
Container appearance, icon sizes and media editing.
A card with an icon and image
A card is a content container with background, border and corner controls. It does not need every element at once. blocks/media-card.html includes an optional image, icon selection and size, title and description. Add it to the package and allow media-card in the parent schema.
1. Card surface
<article data-canvas-card="[[ block.field_path ]].card" style="background:[[ block.settings.background ]];border:[[ block.settings.border_width ]]px solid [[ block.settings.border_color ]];border-radius:[[ block.settings.radius ]]px">
<h2 data-text-field="[[ block.settings_path ]].title" gs-text="block.settings.title"></h2>
</article>data-canvas-card connects the surface editor. Schema additionally exposes explicit color, border width and radius controls with defaults. Never reuse a fixed identifier for every card; block.field_path gives each instance its own path.
Preview appearance overrides are stored in content.canvas.elements. If a schema change appears ineffective, inspect that element's override and reset its style in the editor. These are two configuration layers for the same element.
2. Icon
<span class="launch-card-icon" style="--icon-size:[[ block.settings.icon_size ]]px">
<gs-icon name="[[ block.settings.icon ]]" field="[[ block.field_path ]].icon"></gs-icon>
</span>Use a schema select with real icon names. The catalog contains spark, star, check, arrow, heart, plus, globe, shield, users, bolt, wallet, cards and insights. none means no icon.
Declare size as a range, for example 16–96 px with a 32 px default. CSS reads it through .launch-card-icon svg { width:var(--icon-size,32px); height:var(--icon-size,32px); }. A slider does nothing unless rendering uses its value. The field binding also connects shared icon and dimension editing.
The example omits the icon when set to none. Restore it through schema controls. Do not display a fictitious default icon for an icon-free element.
3. Images and alternative text
<template gs-if="block.settings.image_url != blank">
<img src="[[ safe_url(block.settings.image_url) ]]" alt="[[ block.settings.image_alt ]]" loading="lazy" width="640" height="400">
</template>image_picker currently accepts an image URL. Use HTTPS or a media-library address. image_alt is a separate setting describing the image's meaning; decorative images may use an empty value. A filename is not an informative description.
Set dimensions matching the intended area to reduce layout shifts. The example uses aspect-ratio and object-fit cover. This is CSS cropping, not a saved interactive crop. Shared gs-image works with separate content fields (image_url, adjacent image_alt and image_frame); do not switch these models without explicitly mapping their data.
4. Badges
A badge is a short label, not automatically a link. Declare text with ID badge, a color and radius, then render:
<span class="theme-badge" data-text-field="[[ block.settings_path ]].badge" gs-text="block.settings.badge"></span>Example styling: inline-flex, 6px/12px padding, a one-pixel currentColor border and a 999px radius. For an icon, use gs-icon with a distinct field path and a real none state. If the badge navigates, use an accessible link as described in buttons.
5. Responsive card lists
The parent renders children through gs-slot="blocks"; CSS defines the grid. Cards retain individual settings when reordered. Do not use a screen-sized fixed width for each card. Use minmax and test long content and absent images on mobile.
Final checks
Add differently colored cards, reorder and save them. Hide and restore an icon, resize it, set radius and border width to zero. Check empty and valid image URLs, alt text, surface editing and public rendering. Appearance overrides must not move between instances.
Complete block file
blocks/media-card.html
<article class="launch-card" data-canvas-card="[[ block.field_path ]].card" style="background:[[ block.settings.background ]];border:[[ block.settings.border_width ]]px solid [[ block.settings.border_color ]];border-radius:[[ block.settings.radius ]]px">
<template gs-if="block.settings.icon != 'none'"><span class="launch-card-icon" style="color:[[ block.settings.icon_color ]];--icon-size:[[ block.settings.icon_size ]]px"><gs-icon name="[[ block.settings.icon ]]" field="[[ block.field_path ]].icon"></gs-icon></span></template>
<template gs-if="block.settings.image_url != blank"><img class="launch-card-image" src="[[ safe_url(block.settings.image_url) ]]" alt="[[ block.settings.image_alt ]]" loading="lazy" width="640" height="400"></template>
<h2 data-text-field="[[ block.settings_path ]].title" gs-text="block.settings.title"></h2>
<p data-text-field="[[ block.settings_path ]].text" gs-text="block.settings.text"></p>
</article>