# Engage UI

**Version 1.0.0** - a framework-independent design system extracted from the Engage prototype.

Copy this entire folder into another project. It contains the design tokens, component styles, optional interaction runtime, essential SVG icons, and a standalone starter. It does **not** depend on the prototype's scripts, application state, client data, Font Awesome kit, or a build system.

The full interactive catalog lives at `../component-library.html` in the source project. It documents 58 foundation, component, and pattern entries with live examples and copyable markup.

## Files

| File | Purpose |
| --- | --- |
| `engage.css` | Convenient CSS entry point; imports tokens and components. |
| `tokens.css` | Canonical palette, semantic colors, typography, spacing, shape, elevation, motion, and layering. |
| `components.css` | Namespaced component styles and responsive behavior. |
| `engage.js` | Classic browser script exposing `window.EngageUI`; no module loader required. |
| `engage.d.ts` | TypeScript declarations for the browser-global API. |
| `starter.html`, `starter.css`, `starter.js` | Self-contained consumer example; all dependencies stay inside this folder. |
| `assets/preview.svg` | Original illustrative layout asset used by the catalog; not a screenshot of client data. |

The starter is demonstration application code, not a mandatory part of your app. Production consumers normally load only `engage.css` and `engage.js`. CSS-only components can omit the runtime.

## Quick start

Place `design-system` alongside your HTML and reference it with ordinary relative URLs:

```html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="design-system/engage.css">
</head>
<body>
  <main class="engage" id="my-app">
    <button type="button" class="engage-button engage-button--primary"
      data-engage-toast="Ready to build.">
      <span data-engage-icon="plus"></span>
      Get started
    </button>
  </main>
  <script src="design-system/engage.js"></script>
  <script>
    const ui = EngageUI.init(document.getElementById('my-app'));
  </script>
</body>
</html>
```

With an external application script, load `engage.js` followed by your app script using `defer`, as in `starter.html`. Do not call `EngageUI.init` from an inline script that executes before a deferred `engage.js`.

The starter and gallery work from a static server or by opening their HTML files directly. There is no fetch-based template loading and no required network dependency in the portable folder.

### CSS integration

The `.engage` root opts into the library's base typography and scoped normalization. Individual `.engage-*` component classes can also be adopted incrementally. Importing the stylesheet alone must not restyle arbitrary host buttons, inputs, or page layouts.

If your bundler does not process CSS `@import`, include `tokens.css` and then `components.css` as separate stylesheet links. Keep the relative file structure when copying assets.

The runtime enhances DOM markup; it does not return React/Vue/Svelte components. In a framework application, initialize a stable DOM root and call `destroy()` when it unmounts. Avoid having both your framework and the runtime independently manage the same list, tab, or popup DOM. Framework-native wrappers are not supplied.

## Design foundations

Use semantic tokens instead of duplicating values:

```css
.my-workspace {
  color: var(--engage-color-text);
  background: var(--engage-color-surface);
  border: 1px solid var(--engage-color-border);
  border-radius: var(--engage-radius-md);
  padding: var(--engage-space-6);
}

.my-workspace.engage {
  --engage-color-action: #0c5769;
  --engage-color-action-hover: #094b5a;
  --engage-color-action-subtle: #e6f4f7;
}
```

Override related action, hover, focus, and contrast tokens together when introducing another brand. A new brand palette still needs contrast review; overriding a single color is not an accessibility guarantee.

The existing AI accent is a scoped variant:

```html
<section class="engage" data-engage-theme="ai">
  <button type="button" class="engage-button engage-button--primary">Review summary</button>
</section>
```

This is not dark mode. There is one default light visual system with the existing teal and purple treatments.

### Typography and assets

- The font stack starts with Noto Sans, followed by Segoe UI and system fallbacks. The library does not download or bundle a font.
- The full gallery optionally loads Noto Sans from Google Fonts to match the prototype. The portable starter has no external font request. To match the font exactly, configure an approved font source or self-host an appropriately licensed copy.
- Essential icons are original, simple SVG geometries supplied by `EngageUI.icon` or `[data-engage-icon]`. They do not use the original application's Font Awesome kit.
- Custom icons can be supplied as inline SVGs with `.engage-icon`. Respect licenses for any third-party icon or font files you add.
- No third-party font/icon redistribution rights or new public license are granted by this folder. Repository and organizational permissions still apply.
- All example names, values, and document text are illustrative. No client-specific logos, candidate photos, dossiers, or project data are packaged.

## Component catalog

| Category | Standard names |
| --- | --- |
| Foundations | Color Tokens, Typography, Spacing & Layout, Shape & Elevation, Icon |
| Actions | Button, Icon Button, Text Link |
| Forms | Text Field, Textarea, Search Field, Checkbox, Radio Group, Selection Card, Switch, Select, Multi-select, Combobox, Segmented Control, Rating |
| Content | Card, Section Card, Page Header, Section Heading, Description List, Badge, Chip, Avatar, Empty State |
| Navigation | Tabs, Accordion, Breadcrumbs, Sidebar Navigation, Toolbar, Dropdown Menu |
| Feedback | Alert, Toast, Tooltip, Popover, Modal Dialog, Confirmation Dialog, Drawer, Image Preview / Lightbox, Skeleton, Progress |
| Data | Data Table, Editable / Reorderable List, Statistic, Donut Chart, Bar Chart, Funnel |
| Patterns | Filter Toolbar, Settings Form, App Shell, Profile Header, Dashboard Summary, Document Panel, Chat Layout |

Patterns compose the same exported classes and behaviors; they are not separate business-feature implementations. Charts are lightweight visual patterns, not a general-purpose chart engine.

### Naming and variants

Use descriptive kebab-case class names and `--variant` suffixes:

```html
<button type="button" class="engage-button engage-button--primary">Primary</button>
<button type="button" class="engage-button engage-button--secondary">Secondary</button>
<button type="button" class="engage-button engage-button--ghost">Ghost</button>
<button type="button" class="engage-button engage-button--danger">Remove</button>
<button type="button" class="engage-button engage-button--primary" disabled>Unavailable</button>
```

Button sizes: `--small`, the default size, `--large`, and `--compact` for the original settings density. Use native `disabled`, `checked`, `readonly`, `required`, and validation attributes rather than invented state classes. Use `aria-invalid="true"` with a visible error message when your application detects invalid input.

Native fields, radio groups, switches, selections, segmented controls, and ratings remain usable without JavaScript. The runtime is needed for custom menus, multi-selects, comboboxes, tabs, coordinated disclosures, tooltips, overlays, notifications, table filtering/sorting, and editable lists.

## JavaScript API

```js
const root = document.querySelector('.engage');
const ui = EngageUI.init(root);

// After inserting new component markup:
ui.refresh();

// When unmounting the UI:
ui.destroy();

// Native dialog, drawer, or lightbox:
EngageUI.open(document.getElementById('details-dialog'));
EngageUI.close(document.getElementById('details-dialog'), 'cancel');

// Dialog IDs are also accepted:
EngageUI.open('details-dialog');
// Limit lookup and ownership to a particular mounted root:
EngageUI.open('details-dialog', { root });
EngageUI.close('details-dialog', 'cancel', { root });

// Toast:
const notification = EngageUI.toast('Changes applied locally.', {
  variant: 'success', // success, info, or danger
  duration: 5000,    // 0 keeps it visible until dismissed
  root
});
notification.dismiss();

// An aria-hidden SVG element:
const svg = EngageUI.icon('plus');
document.querySelector('.my-add-button').prepend(svg);
```

`EngageUI.VERSION` is `1.0.0`. Initialization is explicit and idempotent for the same root. Each mounted region owns its listeners and transient UI state. Use separate, non-overlapping roots for independent instances and destroy them before discarding their DOM.

All IDs and radio `name` groups in copied examples must be unique for their intended scope. Never reuse a gallery's IDs for two instances on the same page.

### Events and application ownership

```js
root.addEventListener('engage:change', event => {
  const { component, value } = event.detail;
  // Respond to selection, filtering, sorting, or other component changes.
});

root.addEventListener('engage:save', event => {
  const { items } = event.detail;
  // Editable-list snapshot: [{ name, enabled }, ...].
  // Connect persistence here and surface any failure to the user.
});
```

Native input and change events remain available. The design system does not serialize form state, send requests, perform authentication, route pages, or write to storage. Form submission and validation policy belong to the consuming application.

The Editable List's "Apply locally" action acknowledges edits and emits `engage:save`. That event is **not** confirmation of server persistence. For asynchronous saving, your application should control its own pending/success/error workflow and communicate failures explicitly.

### Behavior contracts

| Component | Opt-in markup and behavior |
| --- | --- |
| Tabs | `[data-engage-tabs]`, native buttons with `role="tab"`, paired `aria-controls` / panel IDs, and `role="tabpanel"`. Arrow keys, Home/End, disabled skipping, and roving focus. |
| Accordion | Native `details` / `summary`. Add `[data-engage-accordion="single"]` around a group for one open disclosure at a time. |
| Dropdown Menu | `[data-engage-menu]`, `[data-engage-trigger]`, and a hidden `[data-engage-panel]` containing `role="menuitem"` buttons with `data-value`. |
| Popover | `[data-engage-popover]` with trigger and panel; panel has `role="dialog"` and an accessible title. Not modal. |
| Multi-select | `[data-engage-multiselect]`, a trigger containing `[data-engage-value]`, a panel of native checkbox labels, and optional sibling `[data-engage-clear]`. |
| Combobox | `[data-engage-combobox]`, an input with combobox semantics, listbox panel with named options and `data-value`, and optional `[data-engage-empty]` status. |
| Tooltip | `[data-engage-tooltip]`, focusable trigger with `aria-describedby`, and a hidden `role="tooltip"` element. |
| Dialog / Drawer / Lightbox | Native `dialog` plus the appropriate class; `[data-engage-open="id"]` opens, `[data-engage-close]` closes; `data-engage-value` supplies the result. |
| Toast | API above, or `[data-engage-toast="Message"]` with optional `data-engage-variant`. |
| Alert / Chip | `[data-engage-dismiss]` inside an alert; `[data-engage-chip-remove]` inside a chip. Removal does not change your application's data model. |
| Clear Search | `[data-engage-clear-field="input-id"]` clears the field, emits input/change, and restores focus to it. |
| Data Table | `[data-engage-table]` wrapper, optional search/filter controls, sort buttons with `data-engage-sort`, optional empty row and live result count. |
| Editable List | `[data-engage-list]` with list items, a cloneable template, add/save/move/remove controls, and visible status. |

Use the HTML catalog for full, working component anatomy rather than guessing a missing attribute.

### Table configuration

`data-engage-sort` on a header button is `text`, `number`, or `date`. Numeric or date display values can have a machine-readable `data-engage-sort-value` on the cell. Prefer ISO dates. Search and the optional native select filter combine. A row's `data-engage-filter-value` matches the filter select's value; an empty select value means all rows.

Mark the no-results row with `data-engage-table-empty` and `hidden`. It is excluded from the data set. Include a `data-engage-table-status` element with `role="status"` for result counts. This is a local table enhancement, not virtualized, paginated, or server-side data management.

### Editable-list configuration

- `data-engage-max="4"`: optional explicit item limit, with a visible limit message.
- `data-engage-new-label="New group"`: initial value for a newly added item.
- `data-engage-locked` on an item: prevents removal, without preventing other permitted edits.
- `data-engage-list-template`: a template with the complete new-item markup, without duplicate IDs.
- `data-engage-list-up` / `data-engage-list-down`: keyboard-accessible reordering in addition to the optional drag handle.

### Dialogs and notifications

Native modal dialogs provide focus containment and an inert background. Closing restores focus to the opener where possible. A confirmation dialog can use `data-engage-backdrop="static"` to prevent backdrop-click dismissal; Escape still cancels. Listen to the native `close` event and inspect `dialog.returnValue` to decide whether to proceed.

Menus, multi-selects, combobox lists, popovers, and tooltips adjust their position at viewport edges and flip above or below their trigger when space is limited. This positioning stays inside the component's owning DOM root and is recalculated on scrolling, resizing, and content-size changes. Avoid placing a floating component inside an ancestor that clips overflow; the library does not portal content outside your root.

Toasts pause while hovered or focused. Persistent errors should normally have visible inline feedback as well; do not rely on a temporary toast for essential recovery instructions.

## Accessibility and supported environment

The library targets current Chrome, Edge, Firefox, and Safari with native `dialog`, CSS custom properties, `:focus-visible`, and `:has` support. Legacy browsers and a dialog polyfill are not included.

Markup uses native form controls where possible. Focus rings, disabled states, reduced motion, responsive wrapping, and meaningful labels are part of the component patterns. Consumers remain responsible for document hierarchy, content, unique IDs, validation wording, color-override contrast, and their complete application's accessibility.

The `.engage-app-shell` is a container, not a global viewport takeover. Tables can scroll horizontally inside their own container on narrow screens. Do not apply the original prototype's `body { overflow: hidden }` or global input rules in another application.

## Relationship to the original prototype

The original app now imports the shared library styles and aliases its legacy color variables to the canonical tokens. Shared helper markup adopts selected namespaced primitives while retaining existing selectors and local application handlers. Page-specific layouts, state keys, saved preferences, and business rules remain in the original app.

| Original helper / treatment | Library name |
| --- | --- |
| `toggleRow` | Switch / Switch Field |
| `ck` | Checkbox |
| `banner` | Alert |
| `selectDrop` | Select |
| `multiSelect` | Multi-select |
| Engagement typeahead | Combobox |
| `higiko` | Section Card |
| `hiwCard` | Informational Card composition |
| `secH`, `secHD` | Section Heading |
| `rowListEditor` | Editable / Reorderable List |
| `.seg-ctrl` | Segmented Control |
| `thumb` / lightbox | Image Preview / Lightbox |
| Success toast | Toast, success variant |

This additive migration avoids rewriting the prototype's workflows or carrying their dependencies into the exported library. Keep future reusable changes in this folder; keep application-specific overrides in application styles.

## Development

From the repository root:

```text
npm run check
npm run test:browser
npm run build
```

`npm run check` runs the existing markup sanity check against this checkout and dependency-free Node contract checks. After installing the repository's development dependencies, `npm run test:browser` starts its own isolated loopback server and browser. Windows uses installed Microsoft Edge by default; other platforms use Playwright Chromium. Install that browser with `npx playwright install chromium` when needed, or set `ENGAGE_BROWSER_CHANNEL` to an installed supported channel.

Alternatively, run `npm run serve` and open `http://127.0.0.1:5500/tests/design-system.browser.html` for the runtime interaction fixture. It reports results in the page and in `window.engageTestResults`, with `complete: true` after completion.

The interactive gallery is `src/component-library.html`; the isolated consumer example is `src/design-system/starter.html`. The build copies the complete `src` tree, including the library and gallery.

There are no runtime packages to install. Public npm/CDN publishing, framework-native wrappers, backend integrations, a new dark theme, and general-purpose document/chart/AI engines are outside this version.
