Canvas Design System
Main Site Tokens

Results Overlay

Reports what a batch operation actually did, as an itemised list the user can read before moving on.

When to Use

Use when: An operation changed several things at once and the user needs the itemised account — agenda cleanup, bulk import, a batch edit. The list is the point: "3 durations rounded, 1 break added".
Don't use when: One thing happened and one line covers it — use Toast. For a failure the user must resolve, use Alert.

Variants

Overlay

Covers its positioned parent — typically a modal body once the operation finishes. It needs a position: relative ancestor or it will cover the wrong thing.

This content sits behind the overlay.

<?= ws_results_overlay([
    'id'         => 'cleanupResults',
    'variant'    => 'overlay',
    'title'      => 'Cleanup complete',
    'icon'       => 'check_circle',
    'buttonText' => 'Done',
    'buttonId'   => 'cleanupDoneBtn',
]) ?>

Inline

Sits in the flow below the content it reports on, with no button and no overlay positioning. Use it when the user stays on the page afterwards.

Cleanup complete
  • Rounded 3 activity durations
  • Added 1 break after 90 minutes
  • Fixed 2 title formatting issues
<?= ws_results_overlay([
    'id'      => 'inlineResults',
    'variant' => 'inline',
    'title'   => 'Cleanup complete',
]) ?>

States

StateBehavior
HiddenThe initial state. The markup renders on page load but stays hidden until you reveal it.
VisibleYou add .visible and set the display. The component ships no JS — there is no wsResultsShow(); revealing and hiding are yours.
PopulatedThe list renders empty. Fill #{id}-list with <li> items from your operation's real result — the component has no knowledge of what happened.
Empty resultAn operation that changed nothing produces an empty list under a "complete" heading, which reads as a bug. Branch on the result count and show a different message.
ButtonOverlay variant only. Rendered with your buttonId so you can bind to it — nothing is bound for you.

Real-World Usage

The Planner's agenda cleanup modal. The overlay is placed inside the modal body, populated from the API response, and revealed when the operation finishes — with the announcement and focus handling the component doesn't provide.

<?php ws_modal_start(['id' => 'cleanup', 'title' => 'Clean up agenda']); ?>
    <div style="position: relative;">
        <?= ws_checkbox_card_group($operations, ['label' => 'What should we clean up?']) ?>
        <?= ws_results_overlay([
            'id'         => 'cleanupResults',
            'title'      => 'Cleanup complete',
            'buttonText' => 'Done',
            'buttonId'   => 'cleanupDone',
        ]) ?>
    </div>
<?php ws_modal_end(); ?>

<script>
async function runCleanup(planId, ops) {
    const res     = await fetch('/api/plan-items.php?action=cleanup', { /* … */ });
    const data    = await res.json();
    const overlay = document.getElementById('cleanupResults');
    const list    = document.getElementById('cleanupResults-list');

    if (!data.changes.length) {          // nothing changed — don't claim success
        wsToastInfo('Nothing needed cleaning up');
        return;
    }

    list.innerHTML = data.changes.map(c => `<li>${escapeHtml(c)}</li>`).join('');
    overlay.classList.add('visible');
    overlay.style.display = 'flex';

    // The component announces nothing and moves no focus — do both.
    overlay.setAttribute('role', 'status');
    document.getElementById('cleanupDone').focus();
}

document.getElementById('cleanupDone').addEventListener('click', () => {
    wsModalClose('cleanup');
    location.reload();
});
</script>

Options

OptionTypeDefaultPurpose
idstring''Required in practice — every other ID derives from it, and it's how you reveal the panel.
variantstring'overlay'overlay | inline
titlestring'Complete'Heading. Name the operation: "Cleanup complete".
iconstring'check_circle'Material icon beside the title
buttonTextstring'Done'Action button label. Overlay variant only.
buttonIdstring'{id}-done'Button ID for binding. Overlay variant only.
listIdstring'{id}-list'The <ul> you populate
classstring''Additional CSS classes

Accessibility

ConcernBehavior
StructureResults are a real <ul>, so once populated the count and items are announced properly.
KeyboardThe overlay's action button is a real button. The inline variant has nothing focusable.
AnnouncementMissing. No role="status" or aria-live is applied, so revealing the overlay is silent to screen-reader users — the operation appears to have done nothing. Set role="status" on the container when you reveal it, as in the example above.
FocusMissing. Focus isn't moved when the overlay appears, so a keyboard user stays wherever they were — behind a panel they can't see. Move focus to the Done button on reveal.
Covering contentThe overlay hides its parent visually but doesn't hide it from assistive tech — no aria-hidden or inert is applied underneath. A screen reader can still reach the covered controls.
IconDecorative. The title carries the outcome, so don't rely on the tick to convey success.
warning This component provides markup only. Announcement, focus management, and inerting the content behind it are all the consumer's responsibility — and all three are easy to forget, which is why they're spelled out in the example.

Tokens

The panel draws on the shared surface, success, radius, and type scales rather than defining its own token set — deliberately, so a results panel matches whatever surface it covers. See Token Reference for current values and the .ws-results rules in components.css for the exact declarations.

CSS Classes

ClassPurpose
.ws-resultsBase panel, hidden until revealed
.ws-results--inlineInline variant — no overlay positioning
.ws-results.visibleRevealed state. Added by your code, not the component.
.ws-results__headerIcon and title row
.ws-results__iconOutcome icon
.ws-results__titleHeading text
.ws-results__listThe <ul> you populate, id="{id}-list"

The class prefix is .ws-results, not .ws-results-overlay — helper and class names differ here, as with the planner rails.

Files

FilePurpose
includes/components/helpers.phpws_results_overlay() helper function
includes/components/results-overlay.phpTemplate — header, empty list, and optional button
includes/components/components.cssStyles (.ws-results rules)

No JS file — reveal, populate, announce, and focus are all consumer-side.