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
reason is what separates a suggestion from a list item.
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.
Great for generating ideas quickly in a structured timeframe
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',
]) ?>| Type | Accent |
|---|---|
exercise | Blue — --planner-exercise-* |
icebreaker | Green — --planner-icebreaker-* |
break | Grey — --planner-break-* |
custom | Purple — --planner-custom-* |
States
| State | Behavior |
|---|---|
| Default | Card on --surface-default with a --border-default outline and the type's icon accent. |
| Hover | Border moves to --border-hover. The card itself isn't clickable — the add button is the target. |
| Focus | On the add button only; the card is not focusable. |
| Without a reason | The 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 category | The category is dropped from the meta row. Breaks typically have none. |
| Accepted / dismissed | Neither 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.
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.
| Option | Type | Default | Purpose |
|---|---|---|---|
id | int|string | '' | The item's identifier. Emitted as data-id — your click handler needs it. |
name | string | '' | Activity name |
type | string | 'exercise' | exercise | icebreaker | break | custom. Drives the accent and is emitted as data-type. |
duration | int | 0 | Minutes. Zero renders as "0 min" rather than being hidden — pass a real value. |
category | string | null | Category name in the meta row |
reason | string | null | Why this is being suggested. Write it about their agenda, not the activity in general. |
icon | string | by type | Overrides the type's default icon |
class | string | '' | Additional CSS classes |
Accessibility
| Concern | Behavior |
|---|---|
| Semantics | A 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 button | Needs 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". |
| Keyboard | The add button is a real button and reachable with Tab. Nothing else in the card is focusable. |
| Focus after acceptance | Removing 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 result | Adding 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 independence | Type 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
| Token | Used for |
|---|---|
--planner-exercise-bg / -color | Exercise accent |
--planner-icebreaker-bg / -color | Icebreaker accent |
--planner-break-bg / -color | Break accent |
--planner-custom-bg / -color | Custom accent |
--surface-default | Card background |
--border-default / --border-hover | Card border, resting and hover |
--color-ink / --color-ink-secondary / --color-ink-muted | Name, meta, and reason text |
--mainsite-primary / --mainsite-dark | Add button |
--gray-50 / --gray-500 | Fallback fill and text for an unrecognised type |
--radius-md | Card and icon corners |
--space-2 / --space-3 | Internal spacing |
--text-caption / --text-small / --text-h3 | Meta, reason, and name type scale |
CSS Classes
| Class | Purpose |
|---|---|
.ws-suggestion-card | Card container. Carries data-id and data-type. |
.ws-suggestion-card--exercise / --icebreaker / --break / --custom | Type accents |
.ws-suggestion-card__header | Icon and name row |
.ws-suggestion-card__icon | Type icon in its tinted holder |
.ws-suggestion-card__content | Text column |
.ws-suggestion-card__name | Activity name |
.ws-suggestion-card__meta | Duration and category row |
.ws-suggestion-card__reason | Rationale line |
.ws-suggestion-card__add | Add button — the hook your click handler binds to |
Files
| File | Purpose |
|---|---|
includes/components/helpers.php | ws_suggestion_card() helper function |
includes/components/suggestion-card.php | Template — type mapping and markup |
includes/components/components.css | Styles (.ws-suggestion-card rules) |
css/planner/base/_variables.css | Source of the --planner-* activity colours |