P
UI Design System Docs

Integration Guide

UI Design System · v1.0 · How to consume this library in your application

Quick Start

Copy this minimal template, swap in your module theme, and start building. All components auto-initialise — no JavaScript calls required for standard use.

HTML — Minimal page template
<!DOCTYPE html>
<html lang="en" data-theme="light" data-ui-module="pulse">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="ui-theme" content="pulse">
    <title>My App</title>

    <!-- Vendor -->
    <link rel="stylesheet" href="https://dev.lib.myoasishealth.ca/ui/v1/vendor/bootstrap/bootstrap.min.css">

    <!-- UI Design System -->
    <link rel="stylesheet" href="https://dev.lib.myoasishealth.ca/ui/v1/css/ui-core.css">
    <link rel="stylesheet" href="https://dev.lib.myoasishealth.ca/ui/v1/css/ui-layout.css">
    <link rel="stylesheet" href="https://dev.lib.myoasishealth.ca/ui/v1/css/ui-components.css">
    <link rel="stylesheet" href="https://dev.lib.myoasishealth.ca/ui/v1/css/ui-utilities.css">

    <!-- Module theme (swap for your module) -->
    <link rel="stylesheet" href="https://dev.lib.myoasishealth.ca/ui/v1/themes/pulse.css">
</head>

<body>
<div class="ui-sidebar-overlay" id="ui-sidebar-overlay"></div>

<div class="ui-wrapper">

    <!-- Header, sidebar, content, footer go here -->
    <!-- See "Page Structure" section below -->

</div>

<!-- Vendor -->
<script src="https://dev.lib.myoasishealth.ca/ui/v1/vendor/bootstrap/bootstrap.bundle.min.js"></script>
<script src="https://dev.lib.myoasishealth.ca/ui/v1/vendor/datatables/dataTables.bootstrap5.min.js"></script>

<!-- UI Design System -->
<script src="https://dev.lib.myoasishealth.ca/ui/v1/js/ui-core.js"></script>
<script src="https://dev.lib.myoasishealth.ca/ui/v1/js/ui-forms.js"></script>
<script src="https://dev.lib.myoasishealth.ca/ui/v1/js/ui-tables.js"></script>
<script src="https://dev.lib.myoasishealth.ca/ui/v1/js/ui-notifications.js"></script>
</body>
</html>
Switch dev.lib.myoasishealth.ca to lib.myoasishealth.ca when deploying to production. No other changes needed.

Asset Reference

All assets are served from the central library server. Load order matters — follow the sequence below.

CSS — load in <head>, in this order

FilePurposeRequired
vendor/bootstrap/bootstrap.min.css Bootstrap 5 base — grid, utilities, resets Yes
vendor/datatables/dataTables.bootstrap5.min.css DataTables Bootstrap 5 skin Tables only
css/ui-core.css Design tokens, typography, dark mode, base resets Yes
css/ui-layout.css App shell — header, sidebar, content area, footer Yes
css/ui-components.css All components — cards, buttons, alerts, badges, forms, modals, toasts Yes
css/ui-utilities.css Spacing, flex, text helpers — .ui-mb-md, .ui-d-flex, etc. Recommended
themes/{module}.css Module primary colour — overrides --ui-primary Yes

JS — load before </body>, in this order

FilePurposeRequired
vendor/bootstrap/bootstrap.bundle.min.js Bootstrap JS (includes Popper) Yes
vendor/datatables/dataTables.bootstrap5.min.js DataTables core + Bootstrap 5 integration Tables only
js/ui-core.js Bootstraps all components — sidebar, modals, alert dismiss, theme toggle. Exposes window.UI. Load first. Yes
js/ui-forms.js Form validation. Auto-initialises [data-ui-validate] forms. If using forms
js/ui-tables.js DataTables wrapper. Auto-initialises [data-ui-table] elements. If using tables
js/ui-notifications.js Toast notifications. Exposes UI.notify(). If using toasts

Minified files

Every CSS and JS file ships with a .min counterpart. Use minified files in production for performance.

Production URLs (minified)
<!-- CSS -->
https://lib.myoasishealth.ca/v1/css/ui-core.min.css
https://lib.myoasishealth.ca/v1/css/ui-layout.min.css
https://lib.myoasishealth.ca/v1/css/ui-components.min.css
https://lib.myoasishealth.ca/v1/css/ui-utilities.min.css
https://lib.myoasishealth.ca/v1/themes/pulse.min.css

<!-- JS -->
https://lib.myoasishealth.ca/v1/js/ui-core.min.js
https://lib.myoasishealth.ca/v1/js/ui-forms.min.js
https://lib.myoasishealth.ca/v1/js/ui-tables.min.js
https://lib.myoasishealth.ca/v1/js/ui-notifications.min.js

Page Structure

The app shell uses a fixed header, collapsible sidebar, scrollable content area, and sticky footer. Copy this HTML skeleton into every page.

HTML — Full page shell
<body>
<!-- Sidebar backdrop (mobile tap-to-close) -->
<div class="ui-sidebar-overlay" id="ui-sidebar-overlay"></div>

<div class="ui-wrapper">

  <!-- ── Header ─────────────────────────────────────── -->
  <header class="ui-header">
    <a href="/" class="ui-header__brand">
      <div class="ui-header__avatar">P</div>
      <span class="ui-header__brand-name">My App</span>
      <span class="ui-header__module-tag">Pulse</span>
    </a>

    <button class="ui-header__toggle" data-ui-sidebar-toggle aria-label="Toggle sidebar">
      ☰
    </button>

    <div class="ui-header__search">
      <input type="search" class="ui-header__search-input" placeholder="Search…">
    </div>

    <div class="ui-header__actions">
      <button class="ui-header__icon-btn" aria-label="Notifications">🔔</button>
      <button class="ui-theme-toggle" data-ui-theme-toggle aria-label="Toggle theme">☀/☾</button>
      <button class="ui-header__user" aria-label="User menu">
        <div class="ui-header__avatar">RS</div>
        <div class="ui-header__user-info">
          <span class="ui-header__user-name">Raheel Sajid</span>
          <span class="ui-header__user-role">Administrator</span>
        </div>
      </button>
    </div>
  </header>

  <div class="ui-body">

    <!-- ── Sidebar ──────────────────────────────────── -->
    <aside class="ui-sidebar" id="ui-sidebar" aria-label="Main navigation">
      <div class="ui-sidebar__inner">
        <nav>
          <ul class="ui-nav">
            <li class="ui-nav__section">Main</li>
            <li>
              <a href="/dashboard" class="ui-nav__link ui-nav__link--active">
                <span class="ui-nav__icon">◉</span>
                <span class="ui-nav__text">Dashboard</span>
              </a>
            </li>
            <li>
              <a href="/patients" class="ui-nav__link">
                <span class="ui-nav__icon">+</span>
                <span class="ui-nav__text">Patients</span>
              </a>
            </li>
          </ul>
        </nav>
        <div class="ui-sidebar__footer">
          <span>v1.0.0</span>
        </div>
      </div>
    </aside>

    <!-- ── Content ──────────────────────────────────── -->
    <main class="ui-content">

      <!-- Page header (breadcrumb + actions) -->
      <div class="ui-page-header">
        <div class="ui-page-header__left">
          <h1 class="ui-page-header__title">Page Title</h1>
          <p class="ui-page-header__breadcrumb">Module · Section</p>
        </div>
        <div class="ui-page-header__actions">
          <button class="ui-btn ui-btn--primary">New Record</button>
        </div>
      </div>

      <!-- Page body -->
      <div class="ui-content__body">
        <!-- Your page content here -->
      </div>

    </main>

  </div><!-- /ui-body -->

  <!-- ── Footer ──────────────────────────────────────── -->
  <footer class="ui-footer">
    <span>© 2025 Oasis Family Health Centre</span>
    <span class="ui-footer__env ui-footer__env--dev">Development</span>
    <span class="ui-footer__version">v1.0.0</span>
  </footer>

</div><!-- /ui-wrapper -->
</body>

Footer environment indicator

Control the coloured dot in the footer with the modifier class on .ui-footer__env:

Footer environment
<span class="ui-footer__env ui-footer__env--dev">Development</span>
<span class="ui-footer__env ui-footer__env--staging">Staging</span>
<span class="ui-footer__env ui-footer__env--prod">Production</span>

Module Themes

Each application loads its own theme CSS file. The theme overrides --ui-primary, --ui-primary-light, and --ui-primary-hover. All other tokens are shared.

Pulse — themes/pulse.css
TRACK — themes/track.css
Security — themes/security.css
Finance — themes/finance.css
Reports — themes/reports.css
Doc Manager — themes/document-manager.css
HTML — applying a theme
<!-- 1. Set the module attribute on <html> -->
<html lang="en" data-theme="light" data-ui-module="pulse">

<!-- 2. Set the meta tag (used by ui-core.js for future programmatic use) -->
<meta name="ui-theme" content="pulse">

<!-- 3. Load the theme CSS after ui-components.css -->
<link rel="stylesheet" href="https://dev.lib.myoasishealth.ca/ui/v1/themes/pulse.css">
The data-ui-module attribute is informational only in v1 — it does not affect CSS. The theme file controls the visual accent colour.

Dark Mode

Two mechanisms work together. You do not need to write any dark-mode CSS — the design system handles it automatically.

MechanismHow it works
System preference When <html> has no data-theme attribute (or data-theme="auto"), dark mode activates automatically if the OS is set to dark. No code needed.
Explicit toggle Add data-ui-theme-toggle to any button. ui-core.js wires it up automatically and persists the choice to localStorage.
Programmatic Call UI.theme.set('dark'), UI.theme.set('light'), or UI.theme.toggle().
JavaScript — theme API
UI.theme.get();          // returns 'light' | 'dark' | 'auto'
UI.theme.set('dark');    // force dark, persists to localStorage
UI.theme.set('light');   // force light
UI.theme.set('auto');    // remove explicit preference, defer to OS
UI.theme.toggle();       // switches between light and dark

// Listen for changes
document.addEventListener('ui:theme:change', function (e) {
    console.log('Theme changed to:', e.detail.theme);
});

JavaScript API

All public methods live on window.UI. Auto-initialisation runs on DOMContentLoaded — for standard use, no manual calls are needed.

UI.modal

Modals
// Open by overlay element ID
UI.modal.open('my-modal');

// Close the currently open modal
UI.modal.close();

// Close a specific modal
UI.modal.close('my-modal');

// Attribute-driven (no JS needed)
<button data-ui-modal-open="my-modal">Open</button>
<button data-ui-modal-close>Close</button>

UI.notify

Toast notifications
// Basic
UI.notify('Record saved.', 'success');

// All options
UI.notify('Message', 'warning', {
    duration:    4000,           // ms; 0 = sticky until dismissed
    position:    'bottom-right', // bottom-right | bottom-left | top-right | top-center
    dismissable: true            // show close button
});

// Shorthand methods
UI.notify.success('Record saved.');
UI.notify.warning('Session expiring soon.');
UI.notify.danger('Connection failed.');
UI.notify.info('Update available.');

// Dismiss programmatically
var toast = UI.notify('Processing…', 'info', { duration: 0 });
toast.dismiss();

UI.form

Forms — manual initialisation for dynamic content
// Auto-init runs on DOMContentLoaded for [data-ui-validate] forms.
// For forms added dynamically after page load:
var form = document.getElementById('my-form');
UI.form.init(form);

// Reset all validation state on a form
UI.form.reset(form);

// Validate a single field programmatically
var field = document.getElementById('email');
var isValid = UI.form.validateField(field); // returns true | false

// Custom error message via data attribute
<input type="email" class="ui-input" data-ui-error="Please enter a valid clinic email.">

UI.sidebar

Sidebar
// Toggle (attribute-driven — no JS needed)
<button data-ui-sidebar-toggle>☰</button>

// Programmatic
UI.sidebar.toggle();  // open/close on mobile; collapse/expand on desktop
UI.sidebar.close();   // always closes

// Desktop collapsed state persists in localStorage automatically

Event System

Components fire CustomEvent using the ui: prefix. Listen with addEventListener on the element or document.

EventFires onDetail payload
ui:form:submit form element { formData: FormData, form: HTMLElement }
ui:form:error form element { form: HTMLElement }
ui:table:row-click table element { row: HTMLElement }
ui:table:export table element { format: string }
ui:notification:dismissed document { id: number }
ui:theme:change document { theme: 'light' | 'dark' | 'auto' }
JavaScript — listening to events
// Handle form submit (receives validated FormData)
document.getElementById('patient-form').addEventListener('ui:form:submit', function (e) {
    var data = Object.fromEntries(e.detail.formData);
    fetch('/api/patients', { method: 'POST', body: JSON.stringify(data) })
        .then(function () { UI.notify('Patient saved.', 'success'); });
});

// Handle table row click
document.getElementById('my-table').addEventListener('ui:table:row-click', function (e) {
    var row = e.detail.row;
    // open a modal, navigate, etc.
});

// React when a toast is dismissed
document.addEventListener('ui:notification:dismissed', function (e) {
    console.log('Toast', e.detail.id, 'dismissed');
});

Form Validation

Built on the browser Constraint Validation API. No configuration needed beyond HTML attributes.

How it works

BehaviourTrigger
Validate field on first blur (if touched or required)User leaves field
Re-validate while typing to clear error earlyUser types in an errored field
Validate all fields, block submit if any failForm submit
Fire ui:form:submit with FormDataAll fields valid on submit
Focus first invalid fieldSubmit with errors

HTML attributes used

HTML — validation attributes
<form data-ui-validate>
  <div class="ui-form-group">

    <!-- Required -->
    <input class="ui-input" required>

    <!-- Email pattern -->
    <input class="ui-input" type="email" required>

    <!-- Min / max length -->
    <input class="ui-input" minlength="3" maxlength="50">

    <!-- Numeric range -->
    <input class="ui-input" type="number" min="1" max="100">

    <!-- Regex pattern -->
    <input class="ui-input" pattern="[A-Z]{2}[0-9]{4}">

    <!-- Custom error message (overrides browser default) -->
    <input class="ui-input" type="email" required
           data-ui-error="Enter a valid @clinic.ca email address.">

    <!-- Required label asterisk -->
    <label class="ui-label ui-label--required" for="name">Full Name</label>

    <!-- Error message container (populated by ui-forms.js) -->
    <span class="ui-form-error"></span>

  </div>
</form>

Icons

Icons are inline SVG — copy from tabler.io/icons as needed. No file download or sprite required. Icons inherit colour via currentColor and scale with em units.

HTML — inline icon
<!-- Copy SVG from tabler.io/icons. Size and colour are inherited. -->
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24"
     fill="none" stroke="currentColor" stroke-width="2"
     stroke-linecap="round" stroke-linejoin="round">
  <path stroke="none" d="M0 0h24v24H0z" fill="none"/>
  <circle cx="12" cy="7" r="4"/>
  <path d="M6 21v-2a4 4 0 0 1 4-4h4a4 4 0 0 1 4 4v2"/>
</svg>

<!-- Inside a button -->
<button class="ui-btn ui-btn--primary">
  <svg ...>...</svg>
  Save Patient
</button>
When adding icons to the codebase for the first time, check if any icon SVGs are already in nearby pages and reuse them to keep consistency.

Versioning

Assets are served under a version path (/v1/). Applications pin to a version and are not affected by updates to other versions.

URL structure
<!-- Always reference a specific version -->
https://lib.myoasishealth.ca/v1/css/ui-core.css
                          ^^^  pin here

<!-- When v2 ships, existing apps remain on /v1/ -->
<!-- Opt-in to /v2/ only after testing -->
https://lib.myoasishealth.ca/v2/css/ui-core.css

Dev vs Production

Switching environments
<!-- Development (change href only — all class names remain identical) -->
<link href="https://dev.lib.myoasishealth.ca/ui/v1/css/ui-core.css" ...>

<!-- Production -->
<link href="https://lib.myoasishealth.ca/v1/css/ui-core.css" ...>

Use a server-side variable or build configuration to swap the base URL. The class names, data attributes, and JavaScript API are identical across environments.

Never reference dev.lib.myoasishealth.ca from a production application. The dev server may be unstable or unavailable.

Adding a New Application

Follow this checklist when integrating a new application for the first time.

  1. Choose a module theme colour. Check the existing themes — Pulse (teal), TRACK (indigo), Security (blue), Finance (green), Reports (orange), Document Manager (slate). Pick a distinct hue and add it to docs/ui-design-system-todo.md under Module Branding.
  2. Create a theme file. Add ui/v1/themes/{module}.css with the three primary colour overrides:
    :root {
        --ui-primary:       #your-colour;
        --ui-primary-light: #your-colour-at-15%-opacity;
        --ui-primary-hover: #your-colour-darkened-10%;
    }
    
    [data-theme="dark"] {
        --ui-primary-light: rgba(r, g, b, 0.2);
    }
  3. Build the minified theme. Run npm run build from the repo root to generate {module}.min.css.
  4. Copy the page shell. Use the Quick Start template above. Set data-ui-module on <html> and the <meta name="ui-theme"> tag to your module name.
  5. Build the sidebar server-side. Render <ul class="ui-nav"> items from your application's route and permission model. Add .ui-nav__link--active on the current page. The UI does not need to know about your routes.
  6. Point to the dev server. Use dev.lib.myoasishealth.ca URLs during development. Switch to lib.myoasishealth.ca before go-live.
  7. Test core behaviours. Verify: sidebar toggle (mobile + desktop), dark mode toggle, form validation, toast notifications. Check at mobile (375px), tablet (768px), and desktop (1280px).
  8. Mark integration complete. Check the application off in the Phase 1 Integration checklist in docs/ui-design-system-todo.md.