Planner Library Rail
The Planner's left column: search, category navigation, and the draggable activity palette.
When to Use
sm, so the palette and the canvas speak the same card language.
<aside> and reuse ws_planner_card() directly instead of forcing it.
Variants
Canonical
All three regions: a search field with its keyboard hint, a category list with dots and counts, and grouped sections of draggable cards. Expect a column of 260–320px.
<?= ws_planner_library_rail([
'search' => ['placeholder' => 'Search 2,418 exercises…', 'kbd' => '⌘K', 'name' => 'q'],
'categories' => [
'title' => 'Library',
'count' => '2,418',
'items' => [
['label' => 'Strategy', 'color' => $cat['color'], 'count' => 284, 'active' => true],
],
],
'sections' => [
['title' => 'Strategy · drag to add', 'meta' => 'top 5', 'items' => [
['title' => 'Lightning Decision Jam', 'type' => 'exercise',
'duration' => '45m', 'meta' => '6-12 ppl'],
]],
],
]) ?>categories table per record — they're data, not phase tokens, which is why they aren't red. Pass $cat['color'] straight through rather than hardcoding.
Minimal — no categories
Search plus item sections. Use it when the rail is already scoped to one category and the nav would be redundant. Passing null for search hides that region too.
<?= ws_planner_library_rail([
'search' => ['placeholder' => 'Search icebreakers…'],
'sections' => [['title' => 'Fast icebreakers', 'items' => $items]],
]) ?>States
| State | Behavior |
|---|---|
| Active category | active => true applies the phase colour, swaps the dot to white, and sets aria-current="page". |
| Category as link vs button | With an href the row is an <a>; without one it's a <button> carrying data-category-id and data-category-name for your click handler. |
| Section collapsed / expanded | Section headers toggle via data-rail-toggle, with aria-expanded and aria-controls kept in sync. Your JS owns the toggle. |
| Show more / less | Long sections emit data-rail-show-more with data-label-more and data-label-less, so the button text swaps without hardcoding strings in JS. |
| Card states | Hover, disabled, and the swap affordance all belong to Planner Card. |
| Empty section | Renders the header with no items. Filter empty sections out before passing them. |
Real-World Usage
The Planner's real left column, built from the category and exercise tables. Items are forwarded wholesale to Planner Card, so any card option can travel in the item array — including attrs for the drag payload.
<?= ws_planner_library_rail([
'search' => ['placeholder' => "Search {$total} exercises…", 'kbd' => '⌘K', 'name' => 'q',
'value' => $_GET['q'] ?? ''],
'categories' => [
'title' => 'Library',
'count' => number_format($total),
'items' => array_map(fn($c) => [
'label' => $c['name'],
'color' => $c['color'],
'count' => $c['exercise_count'],
'active' => $c['id'] === $activeCategoryId,
'id' => $c['id'],
], $categories),
],
'sections' => [[
'title' => $activeCategoryName . ' · drag to add',
'meta' => 'top ' . count($exercises),
'items' => array_map(fn($e) => [
'title' => $e['name'],
'type' => 'exercise',
'duration' => (int) $e['duration'],
'meta' => $e['group_size'],
'attrs' => ['data-exercise-id' => $e['id']], // the drag payload
], $exercises),
]],
]) ?>Options
| Option | Type | Purpose |
|---|---|---|
search | array|null | ['placeholder', 'kbd', 'name', 'value']. null hides the region. kbd is the shortcut hint shown in the field. |
categories | array|null | ['title', 'count', 'items'], each item ['label', 'color', 'count?', 'active?', 'href?', 'id?']. null hides the nav. |
sections | array | [['title', 'meta?', 'items']]. Each item is forwarded to ws_planner_card() with size=sm and draggable=true, so every card option is valid. |
id | string | Element ID |
class | string | Additional CSS classes |
attrs | array | Extra HTML attributes |
Accessibility
The best-wired component in the library — treat its markup as the reference for the other rails.
| Concern | Behavior |
|---|---|
| Landmarks | The search region carries role="search", making it a named landmark screen-reader users can jump to. |
| Lists | Category and item groups use role="list" and role="listitem", so counts and positions are announced. |
| Current category | aria-current="page" on the active row — the state is conveyed without relying on the colour swap. |
| Collapsible sections | Headers pair aria-expanded with aria-controls pointing at {sectionId}-body, the correct disclosure pattern. |
| Search field | Labelled by aria-label taken from the placeholder, so it has a name even though there's no visible label. |
| Decorative icons | Category dots and chevrons carry aria-hidden="true". |
| Drag to add | Known gap: dragging a card to the canvas is pointer-only. A keyboard user can reach and focus a palette card but cannot place it. Provide an "Add to agenda" action as an equivalent path — this is the rail's most significant accessibility limitation. |
Tokens
The rail is a layout shell, so most of its visual weight comes from the cards inside it. Its own rules draw on the shared surface, border, ink, and spacing scales; the activity accents come from --planner-* by way of Planner Card, and category dots take their colour from record data rather than a token.
Rail width is governed by --app-rail-width (320px) in the app shell — see the Planner guide. Don't hardcode a width on the container.
CSS Classes
Note the prefix is .ws-planner-rail, not .ws-planner-library-rail — the helper and the class don't share a name.
| Class | Purpose |
|---|---|
.ws-planner-rail | Rail container |
.ws-planner-rail__search / __search-icon / __search-kbd | Search region and its adornments |
.ws-planner-rail__categories / __cat-grid | Category nav wrappers |
.ws-planner-rail__cat-btn / -name / -count / -icon | A category row and its parts |
.ws-planner-rail__cat-dot | Colour dot, tinted from record data |
.ws-planner-rail__h / __h--toggle / __h-left / __h-right / __h-icon / __h-chevron | Section header and its disclosure control |
.ws-planner-rail__body | Collapsible section body, targeted by aria-controls |
.ws-planner-rail__items | Card list |
.ws-planner-rail__filter | Filter control row |
.ws-planner-rail__instructions | Helper text, e.g. "drag to add" |
.ws-planner-rail__footer / -btn / -label / -btn-icon | Rail footer actions |
Files
| File | Purpose |
|---|---|
includes/components/helpers.php | ws_planner_library_rail() helper function |
includes/components/planner-library-rail.php | Template — regions, ARIA wiring, and the card loop |
includes/components/planner-card.php | Renders every activity in the rail |
includes/components/components.css | Styles (.ws-planner-rail rules) |
css/planner/components/_sidebar.css | Planner-side rail styling |