fal) by default; solid (fas) is reserved for toggled-on / active states.
action
brand
masthead
active rail
success
warning
error
text
surface
#1F68A8
| Subject | Site | Status |
|---|---|---|
| SUB-0001 | Site 12 | Done |
| SUB-0002 | Site 03 | Review |
| SUB-0003 | Site 12 | Query |
A self-contained preview of the elluminate design tokens and component patterns. Two palettes work together: the calm elluminate chrome and the vibrant Data-Color palette for user-meaningful data. Pair this with design.md for the AI-readable spec.
01 elluminate Palette
Used for all UI chrome (shells, buttons, text, borders). Calm by design — the canvas for vibrant data.
#BC8C24 instead of yellow-300.02 Data-Color Palette
For data visualizations, charts, status colors the user owns, and category coding. Showing the canonical highlight stops
(100, 200, 500, 700). Full ramps live in utils/color-palette.scss.
03 Gradients
Used for buttons, masthead surfaces, and severity bars. Applied as background-image.
04 Typography
Roboto, falling back to Helvetica Neue / Helvetica / Arial. The app is dense — body text is 12px, the heading scale is compressed.
The quick brown fox
The quick brown fox jumps over the lazy dog
The quick brown fox jumps over the lazy dog
The quick brown fox jumps over the lazy dog
THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG
THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG
Body copy. The default text color is #1A1A1A. Lines are dense and tight to fit grid-heavy clinical UI.
05 Spacing & Sizing
Standardized heights and dimensions. The app is built on these — never invent custom heights without reason.
| What it is | Value |
|---|---|
| Top masthead height | 50px |
| Tabs row height | 43px |
| Nav item height | 38px |
| Header row height (table / list) | 32px |
| Toolbar height | 32px |
.form-control input height | 32px |
| Native input / textarea height | 28px |
| Body row height (table / list) | 22px |
| Button — small (default) | 24px |
| Button — medium | 32px |
| Button — large | 40px |
| Sidebar width | 220px |
| Border radius (universal) | 4px |
| Scrollbar width | 10px |
| Gridster handle width | 6px |
| Pattern | Value | Notes |
|---|---|---|
.form-group | margin-bottom: 0.5rem | 8px between stacked fields |
.form-row | margin: 0 0 10px 0 | flex row, label + control |
.dialog-content | padding: 20px | flex column, font 14px |
.dialog-buttons | padding: 10px | right-aligned, 1px top border |
| Form group caption | padding: 10px, bg: #e8f1f5 | DevExtreme dx-form-group-caption |
NgbModalOptions.windowClass when opening| Class | Max width | Height | When to use |
|---|---|---|---|
.modal-full-height | any | full minus 56px | Most common — tall dialogs (SCE, specifications, audit) |
.modal-lg | 800px | auto | larger forms |
.modal-md | 600px | auto | default — typical edit/confirmation dialogs |
.modal-xl | (xl) | full minus 80px | rare — full-screen-ish |
.modal-lg2 | 800px | full minus 80px | rare — tall larger forms |
.modal-sm | 400px | auto | rare — small confirmations |
06 Shadows
Listed in production-usage order. Two shadows cover ~95% of the app: --shadow-normal on every card, --shadow-large on every overlay/popover/toast.
0 1px 2px rgba(0,0,0,.2)
0 3px 15px rgba(0,0,0,.4)
0 20px 40px rgba(0,0,0,.4)
0 7px 12px 1px rgba(0,0,0,.4)
0 -1px 2px rgba(0,0,0,.2)
07 Z-Index Scale
| Layer | Z-Index |
|---|---|
| .dx-popup, .dx-overlay-modal | 3000 |
| [aria-label][data-balloon-pos] | 3000 |
| .gridster-item-resizable-handler | 6000 |
| ngb-modal-backdrop | 8996 |
| ngb-modal-window | 8997 |
| .dxc-tooltip | 9000 |
| .dx-popover-wrapper | 9002 |
| .dx-overlay-wrapper (dropdowns) | 9003 |
| .popover | 9004 |
| ::ng-deep .ui-toast (PrimeNG) | 9006 |
| ::ng-deep .dx-invalid-message-content | 9008 |
| .ui-overlaypanel, dx datebox / dropdown overlay | 100001 |
| .tooltip | 100002 |
base/z-index.scss.08 Buttons
The default .btn is the Narrow button (24px, 12px text, weight 500). Variants are listed in production-usage order — the most common ones are first.
fal (Light) · Active / toggled = fas (Solid)
production uses 39+ files of fal vs 12 of fas
fal fa-{name} (light) for almost every icon — toolbar buttons, badge icons, dialog close buttons, disclosure chevrons. Switch to fas fa-{name} (solid) only when the icon represents a toggled-on / active state — filter is applied, item is favorited, notifications are enabled.
Solid icons render in primary blue (
#1F68A8) by default — that's the standard "engaged" cue. The visual shift "thin gray → fat blue" reads as "off → on". Don't override with semantic colors (no yellow on a favorited star, no red on a liked heart) — the blue treatment handles every toggle state. Exceptions: inside .btn-primary / .btn-danger / .badge the solid icon inherits the container's text color; on dark surfaces it goes near-white.
09 Form Elements
Inputs default to 28px tall with a 1px solid #DDDDDD border and 4px radius. On focus the border becomes #1F68A8. Validation errors use a #CD482D border.
#1F68A8
#CD482D. The error message itself goes in an alert at the form level (see §11).
10 Alerts
Inline alerts — Bootstrap-style .alert blocks dropped as a <div> inside dialogs, panels, or forms to surface persistent in-context messages (connection-lost banners, contextual tips, advisories). Not for form validation — validation summaries surface in popup toasts (§16), and empty required fields use red border + placeholder="Required" with no popup. The deprecated .inline-error / .inline-warning text-below-input pattern is gone.
.rounded-0 + .mb-0 for a flush, full-width message at the top of a dialog or panel.
.mb-0 removes the default 16px bottom margin (use when stacking alerts tightly or as the last element). .rounded-0 removes the radius (use for full-width banner alerts inside dialog headers).
placeholder="Required". No popup, no inline alert.#CD482D border plus the placeholder text "Required" together carry the message. Don't add a popup toast or an inline alert on top of this — it would be over-signaling. The placeholder swap is critical: a never-touched field with only a red border doesn't communicate enough.
this.alerts.showErrorToast(response.error.message) for a single server message, or
this.alerts.showModelStateError(response.error.modelState) for a multi-error ModelState response (joins messages with <br/> into one sticky toast).
Never render server-returned errors as an inline .alert.alert-danger at the top of the form — the toast is the canonical surface for anything that came back from the server.
11 Badges
Pill-shaped (10px radius, 22px min-width, 12px text). Listed in production-usage order — success/warning/danger/info/secondary cover almost every badge in the product.
5px right margin automatically12 Tables
Header row 32px tall, body row 22px. Header text 11px, body text 12px. Borders are 1px solid #DDDDDD all around.
.table| Subject ID | Site | Visit | Status | Updated |
|---|---|---|---|---|
| SUB-0001 | Site 12 | Screening | Complete | 2026-04-21 |
| SUB-0002 | Site 03 | Visit 4 | In Review | 2026-04-22 |
| SUB-0003 | Site 12 | Baseline | Query | 2026-04-22 |
| SUB-0004 | Site 07 | EOT | Pending | 2026-04-23 |
| SUB-0005 | Site 03 | Follow-up | Complete | 2026-04-24 |
| KRI | Risk | Score | Trend |
|---|---|---|---|
| Adverse event reporting rate | Low | 0.12 | ↘ |
| Protocol deviation density | Medium | 0.43 | → |
| Query open age | High | 0.81 | ↗ |
| Site enrollment variance | Medium | 0.55 | ↗ |
| Data entry latency | Low | 0.18 | → |
#C8E8FA (data-blue-200). Hover any row above to preview. Tables do not alternate row colors — every body row sits on white.13 Cards & Section Headers
Light blue section header (.light-blue-section-header)
Gray-100 fill, gray-300 border, 4px radius. Used for grouping content.
10px padding, 1px solid gray-200, 100% height. The default content wrapper.
14 Toolbars
Listed in production-usage order. The minimal (transparent) toolbar is by far the most-used — that's the one you reach for in panel headers, widget headers, and inset toolbars. The dark gradient toolbar is rare and reserved for the very top of the page.
<toolbar-widget class="minimal-toolbar"> in production. The hand-rolled HTML above is the underlying CSS contract.15 Masthead
The 50px top app bar — a dedicated component (<ecs-masthead>) with masthead-only chrome elements that must not be substituted for their page-body equivalents (.dropdown-menu, .nav-tabs, gray .btn-icon, etc.). Background #182B3B, secondary text #CADDEE, hover bg #1E4060, divider lines #215887, active rail #F77623, active filter accent #4EE5FB (masthead only).
#4EE5FB) signals an active filter — this color is masthead-only and must not appear elsewhere..ecs-menu-btn opens the 850px mega-menu flyout · .eclinical-logo renders the brand mark at 28px tall.ecs-header#CADDEE · inline fal fa-chevron-right at 10px for path separators · trailing .toolbar-badge for counts.ecs-dropdown14px lighter weight · #CADDEE text · right border #215887 · hover bg #1E4060. NOT the same as .dropdown-menu in §18..ecs-masthead-navigation-dropdowns-container with border-left: 1px solid #215887 and margin-left: 15px when next to the header label. Popover panel (not rendered inline here) is a fixed 300px wide DevExtreme dx-popover with a dx-list of 34px rows; selected row gets a 3px #F77623 left bar..sub-nav-container-horizontal49px tall · white text · 3px #F77623 bottom rail on active item · hover bg #1E4060. NOT the same as .nav-tabs in §17..nav-tabs (§17): nav-tabs are 43px with a 2px rail on a light surface; masthead sub-nav is 49px with a 3px rail on the dark #182B3B bar..filter-pill (dark + light variants).filter-pill-dark) — only inside .ecs-masthead-filters-container.filter-pill-light) — for filter bars on white/light page bodies, not inside the masthead36px tall, two-button (label + ×), 4px radius, line-1 11px / line-2 12px. Only the colors and border swap. Pills inside .ecs-masthead-filters-container auto-promote to dark..ecs-pill-overflow-dropdown56px button with +N label and chevron, behind a 1px #215887 left divider≥ 25% of a pill's width past containerRight − 66px.
The masthead supports two search patterns — pick by intent. Both are content-projection slots ([search] / [searchBy]) so the markup is supplied per-route.
.ecs-masthead-search — single input with magnifier icon on the right (#CADDEE, 12px). Width 240px, height 30px, dark bg #1E4060, white text, 4px radius..ecs-masthead-search-by-btn) — height 30px, min-width 180px, dark #182B3B bg, 1px #215887 border, white text, joined to the search input on the right (radius 4px 0 0 4px). The trailing chevron-down is 10px white. Label is the comma-separated list of selected fields ("Name, Description").
Joined search input picks up radius
0 4px 4px 0 so the two read as one combined widget.
Popover (
.ecs-masthead-search-by-popover) — white bg, 1px #DDDDDD border, 4px radius, overlay shadow. Each row (.search-by-option) is a <label> + <input type="checkbox">, 12px text, 6px 14px padding, hover bg #F0F5FA. Checkbox accent uses action blue #1F68A8.
.ecs-masthead-toolbar.btn-icon.btn-icon-for-dark · active filter icon goes #4EE5FB (masthead-only).toolbar-badgez-index: 9000 · 4px left margin between.badge-warning, .badge-danger, .badge-info) plus .toolbar-badge. For clickable badges, add .toolbar-badge-clickable to get the pointer cursor.| In a Figma design, if you see… | Use |
|---|---|
Dropdown trigger inside the dark 50px bar | .ecs-dropdown + .ecs-dropdown-button — fills 50px, 14px lighter, #CADDEE |
| Dropdown menu in a page body | .dropdown-menu + .dropdown-item (§18) |
Tabs inside the dark 50px bar | .sub-nav-container-horizontal — 49px, white, 3px #F77623 rail |
| Tabs inside a light page body | .nav-tabs — 43px, light, 2px #F77623 rail (§17) |
| Filter pill in the masthead | .filter-pill-dark (auto-applied inside .ecs-masthead-filters-container) |
| Filter pill on a white page | .filter-pill-light |
| Icon button on the dark masthead | .btn .btn-icon .btn-icon-for-dark |
| Icon button on a light page | .btn .btn-icon (default — gray) |
| Active filter icon (any color) | #4EE5FB aqua — masthead only |
Manual +5 overflow chip | Use .ecs-pill-overflow-dropdown — handles measure-and-move |
| Side drawer for app navigation | Use the 850px mega-menu flyout (<ecs-menu>) |
16 Popup Toasts
Transient floating alerts — primarily for server-returned messages (API round-trip). Use cases:
- ★ API round-trip validation errors (the canonical use case) — duplicate names, server-enforced uniqueness, permission denials, conflict errors (HTTP 409), save failures, anything in an API response's error payload. Always a toast, never an inline alert.
- API round-trip success / info — save confirmed, export ready, deletion completed, file uploaded.
- Async events not initiated by the user — signalr push, websocket event, background job complete, server-pushed notification.
- Multi-error model state —
showModelStateError(response.error.modelState)joins all server messages with<br/>into one sticky toast.
Rendered by DevExtreme's dx-toast via the shared AlertsService — never hand-rolled. Position: top-center, 20px from top. Width: 370px. Z-index: 9006. Default display time: 5000ms (error toasts are sticky — they stay until dismissed).
Required-field exception: an empty required field is not announced via toast — and usually never reaches the server (the round-trip is short-circuited client-side). Red border + placeholder="Required" only. See §10 path A.
showSuccessToast() · showToast() (info) · showWarningToast() · showErrorToast() (sticky — for server errors)370px × auto, 15px padding, 4px radius, overlay shadow (0 3px 15px rgba(0,0,0,0.4)), and a subtle gray-gradient background (linear-gradient(150deg, #D0D0D0 0%, #FFFFFF 50%, #D6D6D6 75%)) that reads as a "floating glass" surface. The only difference between variants is the icon color (#1F8915 success · #1F68A8 info · #EC7614 warning · #CD482D error).
constructor(private alerts: AlertsService) {}
// Typical API round-trip — toast on both branches:
this.listingApi.save(payload).subscribe({
next: () => this.alerts.showSuccessToast('Saved. Listing applied to all 17 sites.'),
error: response => {
// Single server-returned message (e.g. duplicate name, permission denied):
this.alerts.showErrorToast(response.error.message);
// OR — multi-error ModelState from a .NET API response
// (joined with <br/> into one sticky toast):
this.alerts.showModelStateError(response.error.modelState);
// Optionally highlight the offending field in the form:
if (response.error.field === 'name') this.nameClasses.error = true;
}
});
// Other server-pushed events:
this.alerts.showToast('Export ready. File is in the downloads panel.'); // info
this.alerts.showWarningToast('Partially saved — 2 sites rejected the update.');
AlertsService from shared/alerts/alerts.service.ts — never call DevExtreme's notify() directly. The service applies the right defaults (5000ms display, top center, error = sticky, close-on-outside-click, close-on-click). Toasts always carry the server's message verbatim — the client is just relaying the API response, not authoring its own copy.
| Use a popup toast (§16) when… | Use an inline alert (§10) when… | Use NO popup when… |
|---|---|---|
| The message came back from the server — API response (success / error / warning), duplicate name, conflict, permission denied, save failed, model-state payload. Also signalr / websocket / job-complete pushes. | The message is a persistent state the client knows locally — connection-lost banners, contextual tips, system-state advisories that hold until they don't. | An empty required field is the only issue (and the round-trip was usually short-circuited before the server was even called) |
| The text is the server's message, relayed verbatim | The text is something the client knows independent of any API call | The visual (.error border + placeholder="Required") is sufficient on its own |
| It's transient — disappears after 5 s (errors stay until dismissed) | It's persistent — stays until the underlying state changes | The signal is in the field itself |
| Examples: "A listing named 'Subjects' already exists.", "You don't have permission to publish.", "Save failed — conflict on row 47.", "Saved.", "Export ready.", "Job complete." | Examples: "Connection lost — reconnect to continue.", "Auto-sync paused.", "Tip: listings inherit permissions from the parent folder." | Empty required: "Listing name" / "Visibility" left blank on submit → red border + "Required" placeholder, nothing else. |
17 Tabs
#F77623) bottom rail and #F7F7F7 background.
18 Dropdown Menus
19 Modals
Use <dialog-container> for new dialogs — that component is wrapped around an <ngb-modal> and standardizes the header / body / footer regions. It's the dominant modal pattern in the product (100+ files). The previews below show the rendered shape; in code you write <dialog-container [title]="…" [hideButtonText]="Cancel" [closeButtonText]="Save" [closeButtonClass]="btn-primary">…</dialog-container>. Backdrop is rgba(0,0,0,.4); modal shadow is 0 20px 40px rgba(0,0,0,.4).
.modal-sm — 400px wide in production)1px solid #E8E8E8 bottom border — not the dark page-toolbar gradient..modal-md — 600px wide in production)NgbModalOptions.windowClass when opening — never as a static HTML class. Listed in production-usage order (most-used first).| Class | Max width | Height | When to use |
|---|---|---|---|
.modal-full-height ★ most-used |
(any width) | full minus 56px |
Tall dialogs — SCE files, specifications, audit log, deliverable assignments. The default reach for any list-heavy or multi-section dialog. |
.modal-lg |
800px |
auto | Larger forms — recent-exports, importer domains, anything wider than a typical edit form but not full-height. |
.modal-md (default) |
600px |
auto | The default — typical edit / confirmation dialogs that don't set a windowClass. Note: elluminate's .modal-md is 600px, not Bootstrap's 500px. |
.modal-xl |
(xl width — viewport-bound) | full minus 80px |
Rare — full-screen-ish dialogs (multi-pane editors, comparison views). |
.modal-lg2 |
800px |
full minus 80px |
Rare — tall larger forms (combines lg width with full-height-style sizing). |
.modal-sm |
400px |
auto | Rare — small confirmation dialogs ("Delete this listing?", "Discard changes?"). Note: elluminate's .modal-sm is 400px, not Bootstrap's 300px. |
32px header with 1px solid #E8E8E8 bottom border (never the dark page-toolbar gradient), 20px body padding, 10px right-aligned footer with a 1px solid #EAEAEA top border, 4px radius, rgba(0,0,0,0.4) backdrop, 0 20px 40px rgba(0,0,0,0.4) shadow.
// Tall list-heavy dialog (most common)
this.modalService.open(MyDialogComponent, {
windowClass: 'modal-full-height',
backdrop: 'static'
});
// Typical edit / confirmation dialog (default — 600px)
this.modalService.open(MyDialogComponent, {
windowClass: 'modal-md'
// or omit windowClass entirely — .modal-md is the default
});
// Small confirmation ("Delete this listing?")
this.modalService.open(ConfirmDialogComponent, {
windowClass: 'modal-sm'
});
windowClass is set on the NgbModalOptions at open time, not as a static class on the modal markup. backdrop: 'static' prevents click-outside dismissal — use for any dialog with unsaved work.
20 Tooltips
Use ng-bootstrap's [ngbTooltip] directive for almost every tooltip in the product (used in 100+ components — toolbars, icon buttons, table cells, badges, form fields). It renders a .tooltip element styled with elluminate's overrides: 12px white text on near-black, 350px max-width.
<button [ngbTooltip]="'Saves and closes the dialog.'">…</button>. Position via placement="top" (default) / "bottom" / "left" / "right".21 Accordion
Header 32px tall on #F6F6F6, body has 0 0 6px 0 padding and 60px minimum height. Disclosure indicator is a chevron, never plus/minus. Click to expand.
Study details
Risk profile
Configuration
RP-CDISC-Standard-v3. EIQ Orchestrator rules: 12 active, 3 disabled.
22 Risk Levels
Use .risk-level-{undefined|low|high-low|low-medium|high-medium|high|extreme-high} — never invent new levels.
23 Indicators
"Thinking" indicator from the Matilda assistant: animated text gradient + breathing dot.
#767676 → #1A1A1A → #767676, 1s linear infinite. Dot is #1F68A8, 2.5s breathe.