Component Inventory
Manifest of shared CSS/JS primitives in Simple WP Helpdesk. Use this as the source of truth before adding new components — prefer extending an existing primitive over inventing a new one.
If a primitive you need is not listed here, check the v4.0 component gaps section at the bottom: it may already be planned (with a tracking issue) and worth waiting on rather than re-implementing locally.
Last refreshed: v3.7.0 (2026-05-13)
Existing primitives (v3.7.0)
.swh-empty-state
Type: CSS class + PHP helper (swh_render_empty_state()) File: simple-wp-helpdesk/assets/swh-shared.css:106-128 PHP helper: simple-wp-helpdesk/includes/helpers.php:796-829 Since: v3.5.0 (CSS), v3.6.0 (PHP helper) Tokens consumed: --swh-space-xl, --swh-space-lg, --swh-space-sm, --swh-space-xs, --swh-space-md, --swh-color-muted, --swh-color-text, --swh-color-text-secondary, --swh-font-md, --swh-font-sm
A11y notes:
- The helper escapes title/desc and accepts a configurable heading level (default
h2) so the empty state slots into surrounding heading hierarchy correctly. Passh3when nested inside a sub-section that already owns anh2. - The icon
<svg>is rendered witharia-hidden="true"— decorative only. <path>markup passed in is filtered throughwp_kses()(onlyd,fill-rule,clip-ruleattributes allowed).- A
:focus-visiblering is applied to anyainside.swh-empty-stateviabody.swh-helpdesk-adminselector inswh-shared.css:193-200.
Required structure:
.swh-empty-state-icon—<svg>(decorative,aria-hidden="true").swh-empty-state-title— heading (h1–h6).swh-empty-state-desc—<p>description- Optional CTA
<a>after the description
Example markup:
<div class="swh-empty-state">
<svg class="swh-empty-state-icon" viewBox="0 0 24 24" aria-hidden="true">
<path d="M12 2L2 22h20L12 2z"/>
</svg>
<h2 class="swh-empty-state-title">No tickets yet</h2>
<p class="swh-empty-state-desc">When someone submits a ticket it will appear here.</p>
<a class="button button-primary" href="...">Configure settings</a>
</div>
PHP helper signature:
swh_render_empty_state( string $title, string $desc, string $icon_svg_path, string $heading_level = 'h2' ): void
.swh-toast
Type: CSS class + JS module (window.swhToast()) CSS file: simple-wp-helpdesk/assets/swh-admin.css:439-498 JS source: simple-wp-helpdesk/assets/src/toast/index.js JS build: simple-wp-helpdesk/assets/dist/toast.js (+ toast.asset.php) PHP enqueue helper: swh_enqueue_toast_script() in includes/helpers.php:853+ Since: v3.5.0 (CSS + inline swhToast()); rebuilt with @wordpress/scripts in v3.7.0 (legacy inline implementation removed — call window.swhToast() which is loaded via the built module). Tokens consumed: --swh-space-lg, --swh-space-sm, --swh-space-md, --swh-color-surface, --swh-color-border, --swh-radius-md, --swh-radius-sm, --swh-shadow-md, --swh-z-toast, --swh-transition-normal, --swh-ease-out, --swh-color-success-accent, --swh-color-danger, --swh-color-primary, --swh-color-text, --swh-color-muted, --swh-color-focus
Variants:
.swh-toast--success— green left border.swh-toast--error— red left border.swh-toast--info— primary blue left border.swh-toast--visible— applied by JS to fade/slide the toast in
A11y notes:
- The dismiss button has its own
:focus-visibleoutline. - The toast container is positioned
bottom-rightwithz-index: var(--swh-z-toast)so it sits above modal/dropdown layers. - Integrators that wire toasts to ARIA live announcements should look at the v3.6.0
aria-liveregion (test_64) instead of relying on toast DOM insertion alone — toasts are visual, not announced by default.
Required structure:
<div class="swh-toast swh-toast--success swh-toast--visible" role="status">
<span class="swh-toast__message">Settings saved.</span>
<button type="button" class="swh-toast__dismiss" aria-label="Dismiss">×</button>
</div>
JS API (v3.7.0+):
window.swhToast( 'Settings saved.', 'success' ); // 'success' | 'error' | 'info'
PHP-side, enqueue the script through swh_enqueue_toast_script() — it reads toast.asset.php for dependencies and the hashed version so the wp_enqueue_script call stays in sync with the build automatically.
.swh-badge
Type: CSS class File: simple-wp-helpdesk/assets/swh-shared.css:77-104 Since: v3.4.0 (consolidated in #330) Tokens consumed: --swh-radius-pill, --swh-font-sm, --swh-transition-fast, --swh-ease-out, --swh-color-focus, --swh-radius-sm, plus status-/SLA-specific colour pairs (see below)
Variants (status, keyed on sanitize_title( $status )):
.swh-badge-open— info blue.swh-badge-closed— error red.swh-badge-in-progress— warning amber.swh-badge-resolved— success green
Variants (SLA):
.swh-badge-sla-warn— warning amber.swh-badge-sla-breach— solid danger with white text
Custom status slugs follow the same pattern: define .swh-badge-<slug> consuming a contrast-safe bg/text pair from the token table.
A11y notes:
:where(a, button)selector applies pointer + brightness-hover feedback only when the badge is interactive.:focus-visiblering lights up via--swh-color-focus.- The white-on-red
sla-breachvariant uses--swh-color-on-solid(white) for contrast against the solid--swh-color-dangerbackground. - A pill is visual-only — pair with
aria-labelif the colour alone conveys status to screen-reader users.
Example markup:
<span class="swh-badge swh-badge-in-progress">In Progress</span>
<a class="swh-badge swh-badge-open" href="...">Open (12)</a>
.swh-bubble
Type: CSS class File: simple-wp-helpdesk/assets/swh-admin.css:94-130 Since: v2.x (conversation UI redesign, #253) Tokens consumed: --swh-space-sm, --swh-space-md, --swh-space-xs, --swh-radius-sm, --swh-color-primary, --swh-color-warning-bg, --swh-color-warning-bd, --swh-color-bg-subtle, --swh-color-border-subtle, --swh-color-info-bg, --swh-color-muted
Variants:
.swh-bubble-note— internal note (yellow background, warning border).swh-bubble-user— client message (subtle grey).swh-bubble-tech— staff reply (info blue)
Sub-elements:
.swh-bubble-meta— block-level meta strip (author, etc.).swh-bubble-timestamp— muted relative time.swh-bubble-attachments— attachment chip row
A11y notes: Bubbles are visual containers — author/timestamp are expressed in text in .swh-bubble-meta. The internal-note variant should be paired with explicit text (“Internal note”) rather than relying on colour alone.
Required structure:
<div class="swh-bubble swh-bubble-tech">
<span class="swh-bubble-meta">
Tech Alice
<span class="swh-bubble-timestamp">2 hours ago</span>
</span>
<p>Sure, I'll look into that today.</p>
<div class="swh-bubble-attachments">...</div>
</div>
.swh-panel-group
Type: CSS class File: simple-wp-helpdesk/assets/swh-admin.css:335-372 Since: v3.4.0 (ticket editor side-panel redesign, #322) Tokens consumed: --swh-space-sm, --swh-space-xs, --swh-color-border-subtle, --swh-font-sm, --swh-color-text-secondary
Sub-element:
.swh-panel-group-label— small uppercase tracking-wide label
A11y notes:
- The label uses font-weight + letter-spacing for visual hierarchy; it is not a heading — the surrounding meta box
<h2>from WP already provides the structural heading. - Do not rename the
nameattributes of inputs inside the group (swh-status,swh-priority,swh-assigned-toIDs in particular) — thesave_posthandler matches on them.
Required structure:
<div class="swh-panel-group">
<span class="swh-panel-group-label">Status</span>
<select id="swh-status" class="swh-select" name="swh_status">...</select>
</div>
<div class="swh-panel-group">
<span class="swh-panel-group-label">Priority</span>
<select id="swh-priority" class="swh-select" name="swh_priority">...</select>
</div>
.swh-skeleton (shimmer loader)
Type: CSS class + @keyframes swh-shimmer File: simple-wp-helpdesk/assets/swh-admin.css:407-437 Since: v3.5.0 (#332) Tokens consumed: --swh-color-surface, --swh-color-bg-subtle, --swh-radius-sm
Variants (sizing wrappers):
.swh-kpi-skeleton— 2rem × 60%, centred margin.swh-chart-skeleton— 200px × 100%
A11y notes:
pointer-events: none; user-select: none;so screen readers / kbd users don’t tab into the placeholder.- Animation respects
prefers-reduced-motion: reducevia the global override inswh-shared.css:166-174. - Pair the skeleton container with
aria-busy="true"on the live region while the real content is loading.
Required structure:
<div class="swh-kpi-card" aria-busy="true">
<div class="swh-skeleton swh-kpi-skeleton"></div>
</div>
.swh-helpdesk-wrapper
Type: CSS class + data-swh-theme attribute File: simple-wp-helpdesk/assets/swh-frontend.css:3-14 (root rules); simple-wp-helpdesk/assets/swh-shared.css:130-163 (dark-mode tokens + light escape hatch); simple-wp-helpdesk/assets/swh-shared.css:185-200 (focus-visible scope) Since: Pre-v3.0 (root frontend wrapper); data-swh-theme added in v3.6.0 (#321) Tokens consumed: all dark-mode-overridable tokens — see token list at end of this doc
Behaviour:
- Default — follows
prefers-color-scheme: darkand remaps--swh-color-bg,--swh-color-text,--swh-color-primary, etc. to dark equivalents. - Add
data-swh-theme="light"on the wrapper to force light mode regardless of OS preference. Driven by theswh_portal_themesetting ('auto'vs'light'), set inclass-portal.phpandclass-shortcode.php.
A11y notes:
- The wrapper is also the scope for
:focus-visiblerings on every SWH-owned interactive element (button, a, select, textarea, input,[role="tab"],[role="radio"],.swh-badge). Stomping this scope would break the v3.6.0 focus-ring contract. - Dark-mode tokens hit WCAG 16:1 (text) and 4.6:1 (secondary) — don’t swap them out for a smaller contrast ratio.
Required structure:
<div class="swh-helpdesk-wrapper" data-swh-theme="light">
<!-- portal / shortcode markup -->
</div>
.swh-skip-link
Type: CSS class File: simple-wp-helpdesk/assets/swh-shared.css:202-233 JS focus handler: simple-wp-helpdesk/assets/swh-admin.js:433+ (delegated click → focus target) Since: v3.6.0 (#341) Tokens consumed: --swh-z-toast, --swh-color-primary, --swh-color-on-solid, --swh-radius-sm, --swh-shadow-md, --swh-color-focus
Behaviour:
- Visually hidden by default using
clip-path: inset(50%)+ 1×1 box (mirrors WP core.screen-reader-textto avoid RTL scroll-width issues). - Becomes visible at top-left on
:focus/:focus-visible. - The accompanying JS handler intercepts the click and calls
.focus()on the target so the browser actually moves the focus ring, not just the scroll position.
A11y notes:
- Must be the first focusable element in the document for the skip-to-content contract to work.
- Target element should have
tabindex="-1"so JS-driven focus succeeds.
Required structure:
<a class="swh-skip-link" href="#swh-main">Skip to content</a>
...
<main id="swh-main" tabindex="-1">...</main>
.swh-danger-zone
Type: CSS class File: simple-wp-helpdesk/assets/swh-admin.css:260-272 Since: v3.4.0 (settings page visual hierarchy, #323) Tokens consumed: --swh-color-danger, --swh-color-danger-surface, --swh-space-md, --swh-space-xl, --swh-radius-sm
A11y notes:
- The
h3inside the zone inherits--swh-color-dangerfor emphasis. Pair destructive actions with an explicitaria-describedbypointing to the explanatory paragraph; colour alone is not a signal. - Submit buttons inside should carry a confirm step (JS
confirm()or a typed-confirmation pattern).
Required structure:
<section class="swh-danger-zone">
<h3>Delete all data on uninstall</h3>
<p id="dz-desc">Removes every ticket, attachment, and setting.</p>
<label><input type="checkbox" name="swh_delete_on_uninstall" aria-describedby="dz-desc"> Yes, delete on uninstall</label>
</section>
.swh-ticket-card-list
Type: CSS class File: simple-wp-helpdesk/assets/swh-frontend.css:412-454+ Since: v3.4.0 (My Tickets portal redesign, #320) Tokens consumed: --swh-space-lg, --swh-space-sm, --swh-space-md, --swh-color-bg, --swh-color-border, --swh-color-border-subtle, --swh-radius-md, --swh-transition-normal, --swh-color-primary, --swh-color-bg-highlight
Sub-elements:
.swh-ticket-card— single row (flex layout, hover lifts the shadow).swh-ticket-card--unread— left primary border + highlight background.swh-ticket-card-body— flexes to fill, truncates title with ellipsis.swh-ticket-card-title— single-line truncated title
A11y notes:
- The list is a
<ul>withlist-style: none— the semantic list role is preserved for AT users. - For “unread” rows, supplement the colour cue with
aria-label="Unread"or a visible “New” pill — colour-only is not accessible.
Required structure:
<ul class="swh-ticket-card-list">
<li class="swh-ticket-card swh-ticket-card--unread">
<div class="swh-ticket-card-body">
<p class="swh-ticket-card-title">Printer offline in lobby</p>
<p class="swh-text-secondary">Opened 2h ago · In Progress</p>
</div>
<a href="...">View</a>
</li>
</ul>
Dark-mode token system
Type: CSS custom-property overrides (no class) Files:
- Frontend portal:
simple-wp-helpdesk/assets/swh-shared.css:130-147(@media (prefers-color-scheme: dark) .swh-helpdesk-wrapper) - Frontend light escape hatch:
simple-wp-helpdesk/assets/swh-shared.css:149-163(.swh-helpdesk-wrapper[data-swh-theme="light"]) - Admin:
simple-wp-helpdesk/assets/swh-shared.css:235-250(body.swh-helpdesk-admin.swh-admin-theme-dark)
Since: v3.6.0 (frontend dark + portal opt-out, #321; admin dark, #339)
Activation rules:
- Frontend — automatic via
prefers-color-scheme: dark. Sites that force light mode setswh_portal_theme = 'light', which addsdata-swh-theme="light"to every.swh-helpdesk-wrapperand overrides the media query. - Admin — opt-in per-user.
swh_admin_color_is_dark()returns true when the WP user’sadmin_colorismidnight,modern, orectoplasm. The settings / reports / ticket-list controllers then add the classswh-admin-theme-darkto<body>(alongside the always-presentswh-helpdesk-adminclass). The dual-class selector (body.swh-helpdesk-admin.swh-admin-theme-dark) is load-bearing — both classes must be present.
Token surface remapped: --swh-color-bg, --swh-color-bg-subtle, --swh-color-surface, --swh-color-bg-highlight, --swh-color-border, --swh-color-border-input, --swh-color-border-subtle, --swh-color-text, --swh-color-text-secondary, --swh-color-muted, --swh-color-primary, --swh-color-track.
A11y notes:
- All dark-mode mappings target WCAG-AA contrast or better against the paired
--swh-color-textvalue. Don’t introduce a new dark variant without re-verifying the contrast ratio for the surface it sits on. - The
prefers-reduced-motionoverride inswh-shared.css:166-174applies regardless of theme — every transition gets clamped to 0.01ms when the user requests reduced motion.
v4.0 component gaps (planned, not yet built)
These primitives do not exist in the codebase today. v4.0 UI work will introduce them. Before reinventing one of these locally, check the linked issue — and prefer reaching for @wordpress/components (which v3.7.0’s @wordpress/scripts build pipeline now makes trivially available) rather than writing a bespoke implementation. The v4.0 indigo-refresh (#355) will retoken every primitive — primitives shipped before #355 lands risk needing a token rewrite.
.swh-modal
Status: Not yet built — needed by v4.0 admin UI work. Tracking issue: part of the v4.0 admin-UX rewrite milestone (#18). Recommended base: @wordpress/components Modal covers focus trap, ESC dismissal, ARIA wiring, and overlay; usually only token-level styling on top is needed. Depends on: indigo-refresh (#355) for final colour tokens.
.swh-drawer (quick-reply)
Status: Not yet built. Tracking issue: #350 — Quick-reply drawer from inbox preview Recommended base: No @wordpress/components equivalent; closest is a custom slide-in panel. Likely needs to be built from scratch using token primitives — start from --swh-shadow-lg, --swh-z-modal, and reuse .swh-panel-group for sectioning. Depends on: indigo-refresh (#355).
.swh-virtual-list (inbox virtualization)
Status: Not yet built. Tracking issue: #349 — Inbox-style Tickets admin page Recommended base: Consider @tanstack/react-virtual or a small bespoke virtualizer; @wordpress/components does not ship virtualization. Token surface will reuse .swh-ticket-card-list styling. Depends on: indigo-refresh (#355) and the v4.0 settings-schema migration (#356) for saved-view persistence.
.swh-command-bar (Ctrl/Cmd+K palette)
Status: Not yet built. Tracking issue: #351 — Command palette (Ctrl/Cmd+K) Recommended base: @wordpress/components ComboboxControl + Modal handle most of the UX; only the action-runner layer and keybinding capture need custom code. Depends on: indigo-refresh (#355).
.swh-tabs (saved views)
Status: Not yet built. Tracking issue: #352 — Saved views (per-user filter persistence) Recommended base: @wordpress/components exports TabPanel; v3.7’s existing [role="tab"] focus-ring scope in swh-shared.css:185-200 already handles keyboard styling. Depends on: indigo-refresh (#355) and per-user storage from settings split (#356).
Appendix — design tokens referenced by the primitives above
All tokens are defined in simple-wp-helpdesk/assets/swh-shared.css:1-75 under the :root selector. Frontend dark mode and admin dark mode each remap a subset (see “Dark-mode token system” entry).
Colours: --swh-color-primary, --swh-color-primary-dark, --swh-color-primary-shadow, --swh-color-danger, --swh-color-danger-dark, --swh-color-danger-surface, --swh-color-success-bg, --swh-color-success-bd, --swh-color-success-text, --swh-color-success-accent, --swh-color-error-bg, --swh-color-error-bd, --swh-color-error-text, --swh-color-warning, --swh-color-warning-bg, --swh-color-warning-bd, --swh-color-warning-text, --swh-color-info-bg, --swh-color-info-bd, --swh-color-info-text, --swh-color-text, --swh-color-text-secondary, --swh-color-muted, --swh-color-border, --swh-color-border-input, --swh-color-border-subtle, --swh-color-bg, --swh-color-bg-subtle, --swh-color-bg-highlight, --swh-color-surface, --swh-color-star, --swh-color-focus, --swh-color-track, --swh-color-note-bg, --swh-color-note-border, --swh-color-note-text, --swh-color-note-bg-hover, --swh-color-note-bd-hover, --swh-color-on-solid.
Radius: --swh-radius-sm (4px), --swh-radius-md (5px), --swh-radius-pill (9999px).
Spacing: --swh-space-xs (5px), --swh-space-sm (10px), --swh-space-md (15px), --swh-space-lg (20px), --swh-space-xl (30px).
Typography: --swh-font-sm (12px), --swh-font-base (15px), --swh-font-md (16px), --swh-line-height (1.6).
Motion: --swh-transition-fast (0.1s), --swh-transition-normal (0.2s), --swh-ease-out, --swh-ease-in-out.
Elevation / shadow: --swh-shadow-sm, --swh-shadow-md, --swh-shadow-lg.
Z-index scale: --swh-z-base (1), --swh-z-dropdown (100), --swh-z-modal (200), --swh-z-toast (300).