Skip to content

Cards

WAGE uses a single, flexible card component — Wage\IconCard — for all card-style layouts. Feature grids, USP blocks, icon lists, inline badges, category cards: they all render through the same component with different props.

This is intentional. One component, many layouts, no ambiguity about which to use.

Wage\IconCard renders an icon (or media), a title, an optional description, and an optional footer. It supports multiple layouts and can be toggled between card (boxed) and inline (flush) styling.

echo new \Wage\IconCard(
icon: 'shield-check',
title: 'Free Returns',
description: 'Don\'t want to sell? We return your items free of charge.',
layout: 'stacked',
boxed: true,
icon_size: 'lg',
);
PropTypeDefaultPurpose
iconstring''SVG name from the registry (e.g. 'shield-check')
titlestring''Card heading (rendered as <h3>)
descriptionstring''Supporting copy
layoutstring'stacked''stacked', 'inline', or 'centered'
boxedboolfalseAdds background, border, padding, and radius
boxed_iconbooltrueGives the icon a boxed background
icon_sizestring'md''sm', 'md', or 'lg'
mediastring''Raw HTML (e.g. an image or custom SVG) — replaces the icon slot
footerclosurenullOptional content rendered after the description
asstring'div'Root element tag — use 'li' when inside a <ul>

Icon above, then title and description. Best for grids where each card deserves attention.

echo new \Wage\IconCard(
icon: 'shield-check',
title: '£750 Insured',
description: 'Every pack is fully insured up to £750 via Royal Mail Special Delivery.',
layout: 'stacked',
boxed: true,
icon_size: 'lg',
);

Icon on the left, text on the right. Good for compact lists, badges, or feature ticks where the user shouldn’t need to stop and read.

echo new \Wage\IconCard(
icon: 'check-circle',
title: 'Next Day Offer',
layout: 'inline',
icon_size: 'md',
);

Icon above, centre-aligned text. Used for standalone feature blocks or hero-adjacent cards.

echo new \Wage\IconCard(
icon: 'star',
title: 'Featured',
description: 'Premium pieces get priority handling.',
layout: 'centered',
boxed: true,
);

The boxed prop controls whether the card has its own visual container (background, border, radius, padding).

Boxed cards use the semantic --card-bg token, which resolves differently based on the surrounding color-scheme:

  • Light mode: semi-transparent white (70% white, 30% transparent) — lets the warm page canvas tint the card
  • Dark mode: 5% white, 95% transparent — subtle lift on dark surfaces

This means you never need to hardcode a card background to handle dark sections. A boxed: true card inside a surface--velvet section will automatically pick up the dark-mode value. If you find yourself writing background-color overrides on cards for dark surfaces, you’re fighting the token — use --card-bg instead (or trust the boxed prop, which already uses it).

Use boxed: true when the card is a distinct content block — USP grids, service categories, feature breakdowns. The box boundary says “this is its own thing.”

Use boxed: false when the card is supporting detail that sits within another block — inline badges in a hero, trust ticks beside a form, compact feature lists.

When cards are in a grid on the same page, standardise on boxed: true with icon_size: 'lg' for consistency. Don’t duplicate box styling (background, border, padding) in section CSS — let the boxed prop handle it.

WAGE does not have a separate IconList component. The pattern of “a list of items with small icons and short labels” is expressed by using IconCard with layout: 'inline' inside a <ul> element.

The as prop lets the card render itself as any HTML element — including <li> for use inside a list:

<ul class="features">
<?php
echo new \Wage\IconCard( icon: 'check-circle', title: 'Free postage', layout: 'inline', as: 'li' );
echo new \Wage\IconCard( icon: 'check-circle', title: 'Fully insured', layout: 'inline', as: 'li' );
echo new \Wage\IconCard( icon: 'check-circle', title: 'Free returns', layout: 'inline', as: 'li' );
echo new \Wage\IconCard( icon: 'check-circle', title: 'No commission', layout: 'inline', as: 'li' );
?>
</ul>

The section’s CSS handles the list’s layout — columns, gap, flex or grid. IconCard doesn’t know or care about its container.

An IconList component would just be IconCard with forced layout: 'inline' and as: 'li'. The only things it could add — multi-column layout, semantic <ul> wrapping — belong in the parent, not in the card itself. Keeping everything in one component avoids the “which do I use here?” question and reduces maintenance surface.

When a group of cards represents a list of items (features, USPs, categories, reviews), wrap them in a <ul> and render each card with as: 'li'. Don’t use <div> wrappers for content that is semantically a list.

<ul class="postal-usps__grid">
<?php
echo new \Wage\IconCard( icon: 'shield-check', title: '£750 Insured', description: '...', boxed: true, icon_size: 'lg', as: 'li' );
echo new \Wage\IconCard( icon: 'map-pin', title: 'Full Tracking', description: '...', boxed: true, icon_size: 'lg', as: 'li' );
echo new \Wage\IconCard( icon: 'banknotes', title: 'No Fees Ever', description: '...', boxed: true, icon_size: 'lg', as: 'li' );
?>
</ul>

When using layout: 'inline' with both a title and a description, the title’s first line is offset slightly downward so it visually aligns with the vertical centre of the icon. This offset scales automatically based on icon_size via CSS :has() selectors — no configuration needed.

When there’s only a title (no description), the alignment switches to align-items: center so the text sits perfectly centred against the icon.

// Title only → centered alignment
echo new \Wage\IconCard( icon: 'check', title: 'Approved', layout: 'inline' );
// Title + description → first line aligns with icon centre
echo new \Wage\IconCard( icon: 'check', title: 'Approved', description: '...', layout: 'inline' );

Both look right without any manual padding adjustments.

The footer prop accepts a closure that renders after the description. Useful for buttons, badges, or inline links inside a card without needing a custom wrapper.

echo new \Wage\IconCard(
icon: 'shield-check',
title: 'Premium Service',
description: 'Priority handling for items valued over £5,000.',
boxed: true,
icon_size: 'lg',
footer: function() {
echo new \Wage\Button( label: 'Learn more', variant: 'ghost-link', icon: 'arrow', href: '/premium/' );
},
);

The closure is invoked during render(), so it has access to anything in the calling scope. Keep footers short — if a card needs complex interactive content, that’s usually a signal to use a more specific component instead.

Pass raw HTML via the media prop to use something other than an SVG icon — an image, a numbered indicator, or custom markup.

$indicator = (string) new \Wage\StepIndicator( number: 1, size: 'lg' );
echo new \Wage\IconCard(
media: $indicator,
title: 'Request Your Pack',
description: 'Fill out our simple form and receive a free valuation pack tomorrow.',
layout: 'inline',
);

When media is set, icon is ignored. The media slot gets the same positioning as the icon slot.

When to reach for IconCard vs custom markup

Section titled “When to reach for IconCard vs custom markup”

Always use IconCard when the pattern is icon (or media) + title + optional description. The component covers:

  • Feature grids
  • USP blocks
  • Category cards
  • Trust badges
  • Inline checklists
  • Step indicators with text
  • Service/offering cards

If you find yourself writing <div><svg>...</svg><h3>...</h3><p>...</p></div>, stop and use IconCard. That’s the exact shape it’s designed for.

Don’t use IconCard when the content is fundamentally different — e.g. a testimonial (use TestimonialCard), a product card (use ProductCard), or a card built around an image rather than an icon. Reach for a specialised component in those cases, or create one if needed.

When placing IconCards in a grid, the wrapping list or div handles layout. IconCard handles the card itself:

<ul class="feature-grid">
<?php foreach ( $features as $feature ) : ?>
<?php echo new \Wage\IconCard(
icon: $feature['icon'],
title: $feature['title'],
description: $feature['desc'],
boxed: true,
icon_size: 'lg',
as: 'li',
); ?>
<?php endforeach; ?>
</ul>
.feature-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: var(--grid-gap);
list-style: none;
padding: 0;
margin-top: var(--container-gap);
}
@media (max-width: 1023px) {
.feature-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 767px) {
.feature-grid { grid-template-columns: 1fr; }
}

The section CSS only defines the grid. The cards are fully styled by the boxed prop.

Like any core component, IconCard can be overridden at the child theme level by creating a class with the same name:

// inc/core/components/icon-card/icon-card.php in the child theme
namespace Wage {
if ( ! class_exists( 'Wage\\IconCard' ) ) {
class IconCard extends Component {
// Custom implementation
}
}
}

The child’s class_exists() guard runs first, preventing the core class from loading. If you only need to tweak visuals, override the CSS file at the same path (inc/core/components/icon-card/icon-card.css) — remember that child CSS overrides replace the core file entirely, so include all rules.