Canvas Design System
Main Site Tokens

Suggestion Card

Proposes an activity for the agenda, with the reason it's being suggested and a one-click way to accept it.

When to Use

Use when: The system is recommending something the user can accept or ignore — Coach suggestions in the Planner, "you might also add" panels, gap-filling prompts. The reason is what separates a suggestion from a list item.
Don't use when: The user is browsing rather than being recommended to — the library palette uses Planner Card. For an item already on the agenda, that's Planner Card too.

Variants

Four type variants, colour-matched to the planner's activity coding so a suggestion reads the same as the card it will become. These colours are the sanctioned exception to Red Unification.

Crazy 8s
Exercise 30 min Ideation

Great for generating ideas quickly in a structured timeframe

Two Truths and a Lie
Icebreaker 10 min Team Building
Coffee Break
Break 15 min
Custom Activity
Custom 20 min

Tailored to your workshop goals

<?= ws_suggestion_card([
    'id'       => 1,
    'name'     => 'Crazy 8s',
    'type'     => 'exercise',
    'duration' => 30,
    'category' => 'Ideation',
    'reason'   => 'Great for generating ideas quickly',
]) ?>
TypeAccent
exerciseBlue — --planner-exercise-*
icebreakerGreen — --planner-icebreaker-*
breakGrey — --planner-break-*
customPurple — --planner-custom-*

States

StateBehavior
DefaultCard on --surface-default with a --border-default outline and the type's icon accent.
HoverBorder moves to --border-hover. The card itself isn't clickable — the add button is the target.
FocusOn the add button only; the card is not focusable.
Without a reasonThe reason line is omitted entirely and the card tightens up. Fine for obvious suggestions, but the reason is the component's whole point — supply one where you can.
Without a categoryThe category is dropped from the meta row. Breaks typically have none.
Accepted / dismissedNeither state exists. The card doesn't know it's been added — remove it from the DOM yourself once the user acts, or it will sit there implying nothing happened.

Real-World Usage

The Coach panel in the Planner's inspector rail. Each card carries data-id and data-type, which is how the click handler knows what to add and to which endpoint.

Assumption Mapping
Exercise 30 min Discovery

Your agenda jumps from framing to solutions with nothing testing the premise

<?php foreach ($coach->suggestions($planId) as $s): ?>
    <?= ws_suggestion_card([
        'id'       => $s['item_id'],
        'name'     => $s['name'],
        'type'     => $s['item_type'],
        'duration' => $s['duration'],
        'category' => $s['category_name'] ?? null,
        'reason'   => $s['rationale'],
    ]) ?>
<?php endforeach; ?>

<script>
document.querySelectorAll('.ws-suggestion-card__add').forEach(btn => {
    btn.addEventListener('click', async () => {
        const card = btn.closest('.ws-suggestion-card');
        await addToAgenda(card.dataset.id, card.dataset.type);
        card.remove();               // the card has no "accepted" state
        wsToastSuccess('Added to agenda');
    });
});
</script>

Options

Everything is passed in one array — ws_suggestion_card() takes no positional arguments, and unlike most helpers the array is required.

OptionTypeDefaultPurpose
idint|string''The item's identifier. Emitted as data-id — your click handler needs it.
namestring''Activity name
typestring'exercise'exercise | icebreaker | break | custom. Drives the accent and is emitted as data-type.
durationint0Minutes. Zero renders as "0 min" rather than being hidden — pass a real value.
categorystringnullCategory name in the meta row
reasonstringnullWhy this is being suggested. Write it about their agenda, not the activity in general.
iconstringby typeOverrides the type's default icon
classstring''Additional CSS classes

Accessibility

ConcernBehavior
SemanticsA plain container with a button inside. No ARIA is applied, and the card itself isn't interactive — which is correct, since only the add action does anything.
Add buttonNeeds your attention. The button is icon-only and gets no aria-label, so it announces as an unnamed button. In a list of suggestions every one sounds identical. Add a label naming the activity: "Add Crazy 8s to agenda".
KeyboardThe add button is a real button and reachable with Tab. Nothing else in the card is focusable.
Focus after acceptanceRemoving the card destroys the focused button and drops focus to <body>. Move focus to the next suggestion, or to the panel heading if none remain.
Announcing the resultAdding an item changes the agenda elsewhere on the page. Confirm it — a Toast or a live region — or the action is silent to non-visual users.
Colour independenceType is conveyed by accent colour and icon. The type word isn't always in the text, so include it in the add button's label if the distinction matters.
<!-- Until the template takes a label, set it after render -->
document.querySelectorAll('.ws-suggestion-card').forEach(card => {
    const name = card.querySelector('.ws-suggestion-card__name').textContent.trim();
    card.querySelector('.ws-suggestion-card__add')
        .setAttribute('aria-label', `Add ${name} to agenda`);
});

Tokens

TokenUsed for
--planner-exercise-bg / -colorExercise accent
--planner-icebreaker-bg / -colorIcebreaker accent
--planner-break-bg / -colorBreak accent
--planner-custom-bg / -colorCustom accent
--surface-defaultCard background
--border-default / --border-hoverCard border, resting and hover
--color-ink / --color-ink-secondary / --color-ink-mutedName, meta, and reason text
--mainsite-primary / --mainsite-darkAdd button
--gray-50 / --gray-500Fallback fill and text for an unrecognised type
--radius-mdCard and icon corners
--space-2 / --space-3Internal spacing
--text-caption / --text-small / --text-h3Meta, reason, and name type scale

CSS Classes

ClassPurpose
.ws-suggestion-cardCard container. Carries data-id and data-type.
.ws-suggestion-card--exercise / --icebreaker / --break / --customType accents
.ws-suggestion-card__headerIcon and name row
.ws-suggestion-card__iconType icon in its tinted holder
.ws-suggestion-card__contentText column
.ws-suggestion-card__nameActivity name
.ws-suggestion-card__metaDuration and category row
.ws-suggestion-card__reasonRationale line
.ws-suggestion-card__addAdd button — the hook your click handler binds to

Files

FilePurpose
includes/components/helpers.phpws_suggestion_card() helper function
includes/components/suggestion-card.phpTemplate — type mapping and markup
includes/components/components.cssStyles (.ws-suggestion-card rules)
css/planner/base/_variables.cssSource of the --planner-* activity colours