Planner Card
The agenda item, in both the shapes the Planner needs: a timeline card and a palette card.
When to Use
lg on the timeline canvas, sm in the library palette. One component keeps the card language identical on both sides of the drag.
Variants
Large — timeline
The canvas card: eyebrow label, title, duration pill, optional drag handle and action icons. Type drives the eyebrow and the accent, and the duration pill inherits that accent — don't override it.
Abstraction Laddering
Two truths and a lie
Morning Coffee Break
Custom Check-in
<?= ws_planner_card('Abstraction Laddering', [
'type' => 'exercise',
'duration' => '60m',
'size' => 'lg',
'draggable' => true,
'actions' => ['swap', 'delete'],
]) ?>Small — palette
The compact draggable source. Title and duration at rest, with a swap icon sliding in on hover or focus-within. Coarse-pointer devices show the icon permanently, since there's no hover to reveal it.
Lightning Decision Jam
Never Have I Ever
Lunch
Client Standup
<?= ws_planner_card('Lightning Decision Jam', [
'type' => 'exercise', 'duration' => '10m', 'size' => 'sm',
]) ?>Small with a meta line
The meta option adds a secondary line to the small card — group size, format, anything that helps a facilitator choose from the palette. It isn't in the demo history but the template supports it.
Assumption Mapping
<?= ws_planner_card('Assumption Mapping', [
'type' => 'exercise', 'duration' => '30m', 'size' => 'sm',
'meta' => '6–12 ppl · remote-friendly',
]) ?>States
Default
Abstraction Laddering
Selected
Abstraction Laddering
Disabled
Abstraction Laddering
| State | Behavior |
|---|---|
| Default | Type accent on a neutral surface. |
| Selected | Accent-coloured border emphasis, plus aria-selected="true". Use it for the card currently open in the inspector. |
| Disabled | Dimmed, with aria-disabled="true". On small cards the hover swap affordance is suppressed — there's nothing to offer. |
| Hover (sm) | The swap icon slides in. Also triggered by :focus-within, so keyboard users get the same affordance. |
| Coarse pointer | The swap icon is always visible — hover-only affordances are unreachable on touch. |
| Dragging | Handled by SortableJS in the Planner, not by this component. draggable only renders the handle. |
<?= ws_planner_card('Abstraction Laddering', [
'type' => 'exercise', 'duration' => '60m',
'state' => 'selected', // default | selected | disabled
]) ?>Real-World Usage
Rendering the agenda from plan_items. The item's ID goes through attrs as a data attribute, which is how the drag handler and the inspector identify what was moved or clicked.
<?php foreach ($planItems as $item): ?>
<?= ws_planner_card($item['title'], [
'type' => $item['item_type'],
'duration' => (int) $item['duration'], // ints format as "60m"
'size' => 'lg',
'state' => $item['id'] === $selectedId ? 'selected' : 'default',
'draggable' => true,
'actions' => ['swap', 'delete'],
'attrs' => ['data-item-id' => $item['id']],
]) ?>
<?php endforeach; ?>
<script>
// Actions are delegated — each carries data-action.
canvas.addEventListener('click', e => {
const btn = e.target.closest('[data-action]');
if (!btn) return;
const id = btn.closest('.ws-planner-card').dataset.itemId;
if (btn.dataset.action === 'delete') removeItem(id);
if (btn.dataset.action === 'swap') openSwapPicker(id);
});
</script>Options
| Option | Type | Default | Purpose |
|---|---|---|---|
$title | string | required | Positional. The activity name. |
type | string | 'exercise' | exercise | icebreaker | break | custom. Drives eyebrow and accent; also emitted as data-type. |
duration | string|int | '' | A label like '60m' or '1h 30m'. Integer minutes are formatted for you. |
size | string | 'lg' | lg (timeline) | sm (palette) |
state | string | 'default' | default | selected | disabled |
draggable | bool | false | Render the drag handle. Large cards only. |
actions | array | [] | swap | delete | edit. Large cards only — the palette deliberately surfaces only its hover swap. |
meta | string | null | Secondary line. Small cards only. |
href | string | null | Renders the card as an <a> |
id | string | null | Element ID |
class | string | '' | Additional CSS classes |
attrs | array | [] | Extra HTML attributes — where data-item-id goes |
Accessibility
One of the more carefully wired components in the set — worth copying from.
| Concern | Behavior |
|---|---|
| ARIA | The card is role="group" with an aria-label composed from title, type, and duration, so it's announced as one coherent item rather than scattered text. |
| State | aria-selected="true" when selected and aria-disabled="true" when disabled — state is conveyed non-visually, not by styling alone. |
| Action buttons | Each has an aria-label, and the action group is labelled "Card actions". The palette's swap is labelled "Swap". |
| Decorative icons | The drag handle and type glyphs carry aria-hidden="true", so they aren't read as stray text. |
| Hover affordance | The small card reveals its swap icon on :focus-within as well as :hover, so keyboard users reach it. Coarse pointers get it permanently. |
| Drag and drop | Known gap: reordering is pointer-only. SortableJS provides no keyboard alternative here, so a keyboard user cannot move an agenda item. Provide move-up/move-down actions or a reorder dialog as an equivalent path. |
| Colour independence | Type is carried by the eyebrow label as well as the accent, so it survives without colour. |
Tokens
The card resolves its type accent into a local --wpc-* set, so internal rules reference one variable rather than branching per type.
| Token | Used for |
|---|---|
--wpc-accent / --wpc-accent-bg | Resolved type accent and its tint |
--wpc-surface / --wpc-border | Card surface and border |
--wpc-text / --wpc-text-muted | Title and secondary text |
--planner-exercise-bg / -color | Exercise accent source |
--planner-icebreaker-bg / -color | Icebreaker accent source |
--planner-break-bg / -color | Break accent source |
--planner-custom-bg / -color | Custom accent source |
--color-surface / --gray-100 / --gray-200 | Neutral surfaces and dividers |
--color-ink / --color-ink-muted / --text-inverse | Text colours |
--font-mono | Duration pill and eyebrow |
--text-nano / --text-micro / --text-caption / --text-small / --text-body / --text-h4 | Type scale across both sizes |
CSS Classes
| Class | Purpose |
|---|---|
.ws-planner-card | Base card, carrying role="group" and data-type |
.ws-planner-card--lg / --sm | Size variants |
.ws-planner-card--exercise / --icebreaker / --break / --custom | Type accents |
.ws-planner-card--selected | Selected emphasis |
.ws-planner-card--disabled | Dimmed, non-interactive |
.ws-planner-card__header / __body / __footer | Layout regions |
.ws-planner-card__eyebrow | Type label above the title |
.ws-planner-card__title | Activity name |
.ws-planner-card__duration | Duration pill, inheriting the accent |
.ws-planner-card__meta | Secondary line (small cards) |
.ws-planner-card__handle | Drag handle |
.ws-planner-card__actions / __action | Action group and buttons, each with data-action |
.ws-planner-card__action--hover | The palette's hover-revealed swap |
.ws-planner-card__row / __row-end | Internal row layout |
Files
| File | Purpose |
|---|---|
includes/components/helpers.php | ws_planner_card() helper function |
includes/components/planner-card.php | Template — accent resolution, ARIA composition, and both size layouts |
includes/components/components.css | Styles (.ws-planner-card rules) |
css/planner/base/_variables.css | Source of the --planner-* activity colours |
css/planner/components/_cards.css | Planner-side card styling that sits alongside this component |