Canvas Design System
Main Site Tokens

Planner Card

The agenda item, in both the shapes the Planner needs: a timeline card and a palette card.

When to Use

Use when: You're representing an activity inside the Planner — lg on the timeline canvas, sm in the library palette. One component keeps the card language identical on both sides of the drag.
Don't use when: The activity is being recommended rather than placed — that's Suggestion Card. For a library listing outside the Planner use Listing Card.

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.

EXERCISES

Abstraction Laddering

ICEBREAKER

Two truths and a lie

BREAK

Morning Coffee Break

CUSTOM

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

10m

Never Have I Ever

5m

Lunch

45m

Client Standup

15m
<?= 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

30m

6–12 ppl · remote-friendly

<?= ws_planner_card('Assumption Mapping', [
    'type' => 'exercise', 'duration' => '30m', 'size' => 'sm',
    'meta' => '6–12 ppl · remote-friendly',
]) ?>

States

Default

EXERCISES

Abstraction Laddering

Selected

EXERCISES

Abstraction Laddering

Disabled

EXERCISES

Abstraction Laddering

StateBehavior
DefaultType accent on a neutral surface.
SelectedAccent-coloured border emphasis, plus aria-selected="true". Use it for the card currently open in the inspector.
DisabledDimmed, 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 pointerThe swap icon is always visible — hover-only affordances are unreachable on touch.
DraggingHandled 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

OptionTypeDefaultPurpose
$titlestringrequiredPositional. The activity name.
typestring'exercise'exercise | icebreaker | break | custom. Drives eyebrow and accent; also emitted as data-type.
durationstring|int''A label like '60m' or '1h 30m'. Integer minutes are formatted for you.
sizestring'lg'lg (timeline) | sm (palette)
statestring'default'default | selected | disabled
draggableboolfalseRender the drag handle. Large cards only.
actionsarray[]swap | delete | edit. Large cards only — the palette deliberately surfaces only its hover swap.
metastringnullSecondary line. Small cards only.
hrefstringnullRenders the card as an <a>
idstringnullElement ID
classstring''Additional CSS classes
attrsarray[]Extra HTML attributes — where data-item-id goes

Accessibility

One of the more carefully wired components in the set — worth copying from.

ConcernBehavior
ARIAThe 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.
Statearia-selected="true" when selected and aria-disabled="true" when disabled — state is conveyed non-visually, not by styling alone.
Action buttonsEach has an aria-label, and the action group is labelled "Card actions". The palette's swap is labelled "Swap".
Decorative iconsThe drag handle and type glyphs carry aria-hidden="true", so they aren't read as stray text.
Hover affordanceThe 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 dropKnown 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 independenceType 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.

TokenUsed for
--wpc-accent / --wpc-accent-bgResolved type accent and its tint
--wpc-surface / --wpc-borderCard surface and border
--wpc-text / --wpc-text-mutedTitle and secondary text
--planner-exercise-bg / -colorExercise accent source
--planner-icebreaker-bg / -colorIcebreaker accent source
--planner-break-bg / -colorBreak accent source
--planner-custom-bg / -colorCustom accent source
--color-surface / --gray-100 / --gray-200Neutral surfaces and dividers
--color-ink / --color-ink-muted / --text-inverseText colours
--font-monoDuration pill and eyebrow
--text-nano / --text-micro / --text-caption / --text-small / --text-body / --text-h4Type scale across both sizes

CSS Classes

ClassPurpose
.ws-planner-cardBase card, carrying role="group" and data-type
.ws-planner-card--lg / --smSize variants
.ws-planner-card--exercise / --icebreaker / --break / --customType accents
.ws-planner-card--selectedSelected emphasis
.ws-planner-card--disabledDimmed, non-interactive
.ws-planner-card__header / __body / __footerLayout regions
.ws-planner-card__eyebrowType label above the title
.ws-planner-card__titleActivity name
.ws-planner-card__durationDuration pill, inheriting the accent
.ws-planner-card__metaSecondary line (small cards)
.ws-planner-card__handleDrag handle
.ws-planner-card__actions / __actionAction group and buttons, each with data-action
.ws-planner-card__action--hoverThe palette's hover-revealed swap
.ws-planner-card__row / __row-endInternal row layout

Files

FilePurpose
includes/components/helpers.phpws_planner_card() helper function
includes/components/planner-card.phpTemplate — accent resolution, ARIA composition, and both size layouts
includes/components/components.cssStyles (.ws-planner-card rules)
css/planner/base/_variables.cssSource of the --planner-* activity colours
css/planner/components/_cards.cssPlanner-side card styling that sits alongside this component