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.
The IconCard component
Section titled “The IconCard component”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',);| Prop | Type | Default | Purpose |
|---|---|---|---|
icon | string | '' | SVG name from the registry (e.g. 'shield-check') |
title | string | '' | Card heading (rendered as <h3>) |
description | string | '' | Supporting copy |
layout | string | 'stacked' | 'stacked', 'inline', or 'centered' |
boxed | bool | false | Adds background, border, padding, and radius |
boxed_icon | bool | true | Gives the icon a boxed background |
icon_size | string | 'md' | 'sm', 'md', or 'lg' |
media | string | '' | Raw HTML (e.g. an image or custom SVG) — replaces the icon slot |
footer | closure | null | Optional content rendered after the description |
as | string | 'div' | Root element tag — use 'li' when inside a <ul> |
Layouts
Section titled “Layouts”Stacked (default)
Section titled “Stacked (default)”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',);Inline
Section titled “Inline”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',);Centered
Section titled “Centered”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,);Boxed vs unboxed
Section titled “Boxed vs unboxed”The boxed prop controls whether the card has its own visual container (background, border, radius, padding).
Theme-aware backgrounds
Section titled “Theme-aware backgrounds”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.
Replacing IconList with IconCard
Section titled “Replacing IconList with IconCard”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.
Why one component, not two
Section titled “Why one component, not two”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.
Semantic lists
Section titled “Semantic lists”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>Icon alignment inside inline cards
Section titled “Icon alignment inside inline cards”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 alignmentecho new \Wage\IconCard( icon: 'check', title: 'Approved', layout: 'inline' );
// Title + description → first line aligns with icon centreecho new \Wage\IconCard( icon: 'check', title: 'Approved', description: '...', layout: 'inline' );Both look right without any manual padding adjustments.
Footer content
Section titled “Footer content”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.
Custom media instead of an icon
Section titled “Custom media instead of an icon”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.
Styling cards in grids
Section titled “Styling cards in grids”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.
Overriding IconCard in a child theme
Section titled “Overriding IconCard in a child theme”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 themenamespace 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.