- Summary
- Using the data adapter
- Basic REST example
- Polling interval
- Handling the AbortSignal
- Error handling
- The CareSummaryData object
- Box layouts
- key-value
- tags
- Flagging incomplete entries
- grouped
- Recording an absence
- Printing
- Print context fields
- Standard print (default)
- Transfer and consultation print
- Refreshing data
- Skeleton loading
- Responsive layout
- Best practices
- Importing
- Properties
- Events
- Methods
- Custom Properties
- CSS Parts
- Dependencies
Care Summary
<ahi-care-summary> | AhiCareSummary
Displays a responsive grid of clinical summary boxes backed by a polling data adapter. Supports key–value, tags, and grouped layouts. Overflowing content is clipped and faded; a unified “Show all / Collapse summary” footer expands or collapses all cards at once. Cards with safety-critical data show critical and review badges in the header.
<!-- Inspect the templates/care-summary/index.html file to see the actual source code for this example -->
<iframe
loading="lazy"
style="visibility:hidden;min-height:280px;" onload="this.style.visibility='visible';"
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
title="Care summary template demo"
src="../../templates/care-summary/">
</iframe>
The care summary displays a responsive grid of clinical summary boxes — active allergies, active problems, medications, recent labs, and any other data your application surfaces. Each box is backed by a polling adapter that handles servers that compute summaries asynchronously (HTTP 202 while pending, 200 when ready).
Using the data adapter
The summary does not accept data directly as a property. Instead, you provide a
CareSummaryDataAdapter that the component calls to fetch summary data. When the server is still
computing the summary, the adapter should throw a CareSummaryPendingError — the component will
keep polling at the configured interval until the data resolves.
interface CareSummaryDataAdapter {
fetchSummary(input: {
/** Abort the in-flight request when this fires. */
signal?: AbortSignal;
}): Promise<CareSummaryData>;
}
If the server returns HTTP 202 (accepted, not yet ready), throw CareSummaryPendingError instead
of returning data. The component catches this specific error class and schedules another poll — it does not
enter an error state.
Basic REST example
import { CareSummaryPendingError } from '@azaleahealth/azalea-kit-ui';
const adapter = {
async fetchSummary({ signal }) {
const res = await fetch('/api/patients/123/summary', { signal });
if (res.status === 202) throw new CareSummaryPendingError();
if (!res.ok) throw new Error(`Failed to load summary: ${res.statusText}`);
return res.json();
}
};
document.querySelector('ahi-care-summary').adapter = adapter;
Polling interval
The component waits pollInterval milliseconds between retries. The default is 3000 ms (3 s).
Adjust it to match your backend’s expected computation time:
<!-- Poll every 5 seconds -->
<ahi-care-summary poll-interval="5000"></ahi-care-summary>
Handling the AbortSignal
The signal fires when the component unmounts or when the adapter property is
replaced. Pass it to fetch so in-flight requests cancel automatically:
const res = await fetch(url, { signal });
Error handling
Any error that is not a CareSummaryPendingError puts the component into an error state: the
failure is normalized into a CareError — the thrown error’s message becomes the
user-facing text and its stacktrace (when available) is captured into details — rendered in the
error region above the summary grid, and emitted as ahi-care-summary-error. Use this to surface
permanent failures such as authorization errors or malformed responses:
if (res.status === 403) throw new Error('You do not have permission to view this patient summary.');
<ahi-care-summary id="summary-error-demo"></ahi-care-summary>
<script>
document.querySelector('#summary-error-demo').adapter = {
fetchSummary() {
return Promise.reject(new Error('You do not have permission to view this patient summary.'));
}
};
</script>
Surfacing errors from outside the adapter
If you fetch supplementary data outside the adapter — or one of several upstream sources fails while the
rest succeed — pass those failures to the errors property and they render in the same error
region as adapter errors, without hiding loaded summary boxes. Each entry is a CareError:
interface CareError {
/** User-facing message describing the failure. */
message: string;
/**
* Optional technical diagnostic (e.g. a stacktrace) revealed through a
* per-error disclosure that is collapsed by default. Always rendered as
* plain text. When omitted, the error renders as a message-only row with
* no disclosure control.
*/
details?: string;
}
const summary = document.querySelector('ahi-care-summary');
summary.errors = [
{
message: 'Active Allergies could not be loaded.',
details:
'Error: ETIMEDOUT\n at fetchAllergies (allergy-client.js:18:9)\n at async loadSupplementary (page.js:9:3)'
},
{ message: 'External lab source timed out.' }
];
Entries with details render a “Show details” toggle that expands to the raw text in a
scrollable monospace block. Each assignment to errors replaces the full list — the component
does not accumulate entries across calls, so if you have multiple independent sources of consumer-supplied
errors, merge them into one array yourself before assigning. Set errors to [] (or
null) to remove consumer-supplied entries; an adapter error is managed by the component itself
and clears when the next refresh() (or adapter change) starts a new polling cycle. The error
region is exposed as the errors CSS part.
The demo below simulates a partial failure: the allergy source timed out, so the adapter returns the Active
Allergies box with an empty groups array (rendering “No data available”) while the other boxes
loaded normally. The failure itself is surfaced via errors (not the adapter, since the rest of
the summary loaded fine) with details attached. A second, details-less error is
included to show the message-only row:
<ahi-care-summary id="summary-consumer-errors-demo"></ahi-care-summary>
<script>
const consumerErrorsSummary = document.querySelector('#summary-consumer-errors-demo');
consumerErrorsSummary.adapter = {
fetchSummary() {
return Promise.resolve({
boxes: [
{
layout: 'grouped',
title: 'Active Allergies',
accentColor: '#b15a00' /* --ahi-color-orange-700 */,
groups: []
},
{
layout: 'key-value',
title: 'Active Problems',
accentColor: '#d00001' /* --ahi-color-red-700 */,
fields: [
{ label: 'Essential Hypertension', value: 'Since May 2024' },
{ label: 'Type 2 Diabetes Mellitus', value: 'Since Feb 2024' },
{ label: 'Hyperlipidemia', value: 'LDL 168 ↑', valueColor: '#d00001' /* --ahi-color-red-700 */ }
]
},
{
layout: 'tags',
title: 'Active Medications',
accentColor: '#8a55a1' /* --ahi-color-purple-600 */,
tags: [
{ label: 'Lisinopril 20mg', color: '#dec5eb' /* --ahi-color-purple-200 */ },
{ label: 'Metoprolol Succinate ER 25mg', color: '#dec5eb' /* --ahi-color-purple-200 */ },
{ label: 'ASA 81mg', color: '#dec5eb' /* --ahi-color-purple-200 */ }
]
},
{
layout: 'grouped',
title: 'Recent Labs',
accentColor: '#007d8a' /* --ahi-color-teal-600 */,
groups: [
{
label: 'Lipid Panel – Apr, 2025',
rows: [
{ label: 'LDL Cholesterol', value: '168', valueSuffix: 'mg/dL', indicator: '↑', valueColor: '#a70002' /* --ahi-color-red-800 */ },
{ label: 'HDL Cholesterol', value: '42', valueSuffix: 'mg/dL', indicator: '↓', valueColor: '#b15a00' /* --ahi-color-orange-700 */ }
]
}
]
}
]
});
}
};
consumerErrorsSummary.errors = [
{
message: 'Active Allergies could not be loaded.',
details: 'Error: ETIMEDOUT\n at fetchAllergies (allergy-client.js:18:9)\n at async loadSupplementary (page.js:9:3)'
},
{ message: 'External lab source timed out.' }
];
</script>
Set the adapter property via JavaScript, not as an HTML attribute. Setting it on the DOM node
before the custom element is upgraded still works — the component reads the property during
connectedCallback and begins polling automatically.
The CareSummaryData object
The adapter’s fetchSummary method must return a CareSummaryData object containing
an array of boxes. Each box has a discriminated layout field that determines how its content is
rendered.
interface CareSummaryData {
boxes: CareSummaryBox[];
}
type CareSummaryBox = CareSummaryKeyValueBox | CareSummaryTagsBox | CareSummaryGroupedBox;
interface CareSummaryKeyValueBox {
layout: 'key-value';
/** Heading shown in the box header. */
title: string;
/**
* CSS color for the 4px accent bar at the top of the box.
* Defaults to `var(--ahi-border)` when omitted.
*/
accentColor?: string;
fields: CareSummaryField[];
}
interface CareSummaryField {
/** Left-side row label. */
label: string;
/** Right-side value. */
value: string;
/** CSS color applied to the value text. */
valueColor?: string;
/** Subdued text appended after the value (e.g. `'mg/dL'`). */
valueSuffix?: string;
/** Trend indicator appended after the value (e.g. `'↑'` or `'↓'`); inherits `valueColor`. */
indicator?: string;
}
interface CareSummaryTagsBox {
layout: 'tags';
/** Heading shown in the box header. */
title: string;
/**
* CSS color for the 4px accent bar at the top of the box.
* Defaults to `var(--ahi-border)` when omitted.
*/
accentColor?: string;
tags: CareSummaryTag[];
}
interface CareSummaryTag {
label: string;
/**
* Background color as any valid CSS color value (e.g. `'#d00001'`, `'hsl(0 100% 41%)'`).
* The component automatically selects black or white text using the APCA algorithm (WCAG 3 draft).
* Omit for a neutral default pill.
*/
color?: string;
/**
* Optional supplemental detail shown in a panel below the tag list when the tag
* is tapped/clicked. Useful for SIG directions, allergy reaction notes, or any
* secondary information that shouldn't clutter the default view. Tags with a
* `detail` value render as interactive buttons with an info-circle icon, or an
* alert-triangle icon when `warning` is also set.
*/
detail?: string;
/**
* When `true`, marks this tag as an assertion that something is *absent* rather
* than an actual entry — e.g. "No Known Medications". Negation tags contribute 0
* to the header count and are excluded from the review-badge and print alert-band
* tallies. The tag still renders normally and still occupies a `max-tags` slot.
*/
negation?: boolean;
/**
* When `true`, signals that this tag entry has a safety problem — missing dose,
* frequency, or other safety-critical data. Renders an alert-triangle icon and a
* dashed border (derived from `color` darkened 20%, or `currentColor` when no
* `color` is set). Screen readers announce "incomplete entry" alongside the label.
*
* Warning tags are always sorted before non-warning tags and are never hidden by
* `max-tags` truncation regardless of their position in the source data. Never
* rely on color alone — the icon and border provide two additional non-color
* signals.
*
* Can be combined with `detail` to provide both the warning flag and tapable
* detail text (e.g. an explanation of what is missing).
*/
warning?: boolean;
}
interface CareSummaryGroupedBox {
layout: 'grouped';
/** Heading shown in the box header. */
title: string;
/**
* CSS color for the 4px accent bar at the top of the box.
* Defaults to `var(--ahi-border)` when omitted.
*/
accentColor?: string;
/**
* When `true`, groups render in the exact source order supplied and the
* `critical: true` priority treatment is disabled for this box: critical groups
* are no longer hoisted to the top and are clipped by `max-groups` like any
* other group. Critical styling and the safety badges / print alert band are
* unaffected. Defaults to `false`.
*/
preserveGroupOrder?: boolean;
groups: CareSummaryGroup[];
}
interface CareSummaryGroup {
/** Group heading shown in the tinted header bar (e.g. substance, panel name, body system). */
label: string;
/**
* When `true`, this group is always visible regardless of `max-groups` truncation
* and renders before non-critical groups. Use for safety-critical entries
* (e.g. life-threatening allergies) that must never be hidden when a card is collapsed.
*/
critical?: boolean;
/**
* Optional supplemental text shown on the right of the group header in a
* subdued style (e.g. onset date, total count).
*/
detail?: string;
/**
* When `true`, marks this group as an assertion that something is *absent* rather
* than an actual entry — e.g. "No Known Allergies". Negation groups contribute 0
* to the header count and are excluded from the safety-badge and print alert-band
* tallies even if `critical` is also set. The group still renders normally and
* still occupies a `max-groups` slot.
*/
negation?: boolean;
rows: CareSummaryGroupRow[];
}
interface CareSummaryGroupRow {
/** Primary label shown on the left. */
label: string;
/** Optional value shown on the right (e.g. a lab result). */
value?: string;
/** CSS color applied to the value text. */
valueColor?: string;
/** Subdued text appended after the value (e.g. `'mg/dL'`). */
valueSuffix?: string;
/** Trend indicator appended after the value (e.g. `'↑'` or `'↓'`); inherits `valueColor`. */
indicator?: string;
/**
* Short text label shown on the right (e.g. `'Life-threatening'`, `'Active'`, `'Resolved'`).
* Always rendered as visible text — `annotationColor` is additive only and must never be
* the sole indicator of meaning.
*/
annotation?: string;
/**
* Color applied to the left border accent of the row and the annotation text as a visual
* severity cue (e.g. `'#d00001'` for life-threatening, `'#b15a00'` for severe).
* Always pair with `annotation` text — never the sole indicator of meaning.
* Omit to use the default neutral border and subdued text color.
*/
annotationColor?: string;
}
The number, order, and content of boxes is entirely controlled by your adapter. There is no fixed list of box types.
Box layouts
Each box in the boxes array must have a layout field set to
'key-value', 'tags', or 'grouped'.
key-value
Renders a two-column grid of label/value rows. Labels wrap freely in the left column; values stay right-aligned on the first line. Use for structured clinical data like lab values, problem dates, or vital signs.
<ahi-care-summary id="kv-demo" skeleton-count="1" max-rows="3" style="--summary-columns:1;max-width:360px;"></ahi-care-summary>
<script>
document.querySelector('#kv-demo').adapter = {
fetchSummary() {
return Promise.resolve({
boxes: [{
layout: 'key-value',
title: 'Active Problems',
accentColor: '#ff6452' /* --ahi-color-red-500 */,
fields: [
{ label: 'Heart Failure with Reduced Ejection Fraction (HFrEF)', value: 'EF 35% · Since Aug 2023' },
{ label: 'Essential Hypertension', value: 'Since 2018' },
{ label: 'Type 2 Diabetes Mellitus', value: 'HbA1c 7.2% · Since 2019' },
{ label: 'Hyperlipidemia', value: 'LDL 138 ↑ · Since 2018', valueColor: '#a70002' /* --ahi-color-red-800 */ },
{ label: 'Chronic Kidney Disease, Stage 3a', value: 'eGFR 52 · Since Jan 2024' },
]
}]
});
}
};
</script>
tags
Renders a wrapping row of colored pill labels. Use for lists whose items don’t have a paired value, such as active medications. Tags support two optional features:
-
detail— tags with adetailstring render as interactive buttons with an info-circle icon. Tapping one opens a panel below the tag list showing that text. Use for on-demand information like SIG directions that would clutter the default view. -
warning— set totrueto flag an entry that has missing safety-critical data. Warning tags always appear first in the list and are never hidden bymax-tagstruncation. See Flagging incomplete entries below.
<ahi-care-summary id="tags-demo" skeleton-count="1" max-tags="4" style="--summary-columns:1;max-width:360px;"></ahi-care-summary>
<script>
document.querySelector('#tags-demo').adapter = {
fetchSummary() {
return Promise.resolve({
boxes: [{
layout: 'tags',
title: 'Active Medications',
accentColor: '#8a55a1' /* --ahi-color-purple-600 */,
tags: [
{ label: 'Warfarin', color: '#fccdad' /* --ahi-color-orange-300 */, warning: true, detail: 'Incomplete entry: dose not specified. Verify and update before dispensing.' },
{ label: 'Lisinopril 20mg', color: '#dec5eb' /* --ahi-color-purple-200 */, detail: 'Take 1 tablet by mouth once daily.' },
{ label: 'Metoprolol 25mg', color: '#dec5eb' /* --ahi-color-purple-200 */ },
{ label: 'Atorvastatin 40mg', color: '#dec5eb' /* --ahi-color-purple-200 */ },
{ label: 'Amlodipine 5mg', color: '#dec5eb' /* --ahi-color-purple-200 */ },
{ label: 'Omeprazole 20mg', color: '#dec5eb' /* --ahi-color-purple-200 */ },
{ label: 'Levothyroxine 50mcg', color: '#dec5eb' /* --ahi-color-purple-200 */ },
{ label: 'Furosemide 40mg', color: '#dec5eb' /* --ahi-color-purple-200 */ },
]
}]
});
}
};
</script>
Flagging incomplete entries
Set warning: true on a tag to signal that a medication entry is missing safety-critical data
such as a dose or frequency. The component renders three non-color signals simultaneously so the flag is
accessible regardless of color perception or display settings:
- An alert-triangle icon always visible inside the pill
- An amber dashed border around the pill
- A screen reader label that announces “incomplete entry” alongside the medication name
Warning tags are always sorted to the front of the tag list regardless of the order they appear in the data,
and are never hidden by max-tags truncation. This ensures a clinician sees the flag immediately
without needing to expand the list.
<ahi-care-summary id="warning-demo" skeleton-count="1" max-tags="3" style="--summary-columns:1;max-width:360px;"></ahi-care-summary>
<script>
document.querySelector('#warning-demo').adapter = {
fetchSummary() {
return Promise.resolve({
boxes: [{
layout: 'tags',
title: 'Active Medications',
accentColor: '#8a55a1' /* --ahi-color-purple-600 */,
tags: [
{ label: 'Lisinopril 20mg', color: '#dec5eb' /* --ahi-color-purple-200 */, detail: 'Take 1 tablet by mouth once daily.' },
{ label: 'Metoprolol 25mg', color: '#dec5eb' /* --ahi-color-purple-200 */ },
{ label: 'Atorvastatin 40mg', color: '#dec5eb' /* --ahi-color-purple-200 */ },
{ label: 'Warfarin', color: '#fccdad' /* --ahi-color-orange-300 */, warning: true, detail: 'Incomplete entry: dose not specified. Verify and update before dispensing.' },
{ label: 'Furosemide 40mg', color: '#dec5eb' /* --ahi-color-purple-200 */ },
]
}]
});
}
};
</script>
Note that Warfarin is defined last in the array above but appears first in the rendered list because
warning: true is set, and remains visible even though max-tags="3" would otherwise
hide it.
grouped
Renders rows organized into labeled groups, each with a tinted full-bleed header bar. Groups support two complementary row display modes that can be used independently or together on the same row:
-
Value mode — a right-aligned value with optional
valueSuffix,indicator, andvalueColor. Use for lab results, measurements, and numeric clinical data. -
Annotation mode — a right-aligned text label with a colored left-border accent,
controlled by
annotationandannotationColor. Use for severity or status labels where color reinforces but never replaces the text.
Use this layout when data has a parent–child structure: allergies (substance → reactions with severity), active problems (body system → diagnoses with status), lab results (panel → values with flags).
<ahi-care-summary id="grouped-labs-demo" skeleton-count="1" max-groups="1" max-rows="2" style="--summary-columns:1;max-width:360px;"></ahi-care-summary>
<script>
document.querySelector('#grouped-labs-demo').adapter = {
fetchSummary() {
return Promise.resolve({
boxes: [{
layout: 'grouped',
title: 'Recent Labs',
accentColor: '#007d8a' /* --ahi-color-teal-600 */,
groups: [
{
label: 'Cardiac — Nov 7, 2024',
rows: [
{ label: 'BNP', value: '892', valueSuffix: 'pg/mL', indicator: '↑', valueColor: '#a70002' /* --ahi-color-red-800 */ },
{ label: 'Troponin I', value: '0.03', valueSuffix: 'ng/mL' },
]
},
{
label: 'Metabolic — Nov 1, 2024',
rows: [
{ label: 'HbA1c', value: '7.2', valueSuffix: '%', indicator: '↑', valueColor: '#b15a00' /* --ahi-color-orange-700 */ },
{ label: 'eGFR', value: '52', valueSuffix: 'mL/min/1.73m²', indicator: '↓', valueColor: '#b15a00' /* --ahi-color-orange-700 */ },
]
},
]
}]
});
}
};
</script>
A row can carry both at once, which is how a lab result says something its value cannot. When a lab returns
no interpretation there is no flag to color and no arrow to draw, so an un-interpreted result would
otherwise look exactly like a normal one — plain text either way. An annotation next to the
value names the gap instead:
<ahi-care-summary id="grouped-uninterpreted-demo" skeleton-count="1" max-groups="1" style="--summary-columns:1;max-width:360px;"></ahi-care-summary>
<script>
document.querySelector('#grouped-uninterpreted-demo').adapter = {
fetchSummary() {
return Promise.resolve({
boxes: [{
layout: 'grouped',
title: 'Recent Labs',
accentColor: '#007d8a' /* --ahi-color-teal-600 */,
groups: [
{
label: 'Chemistry — Aug 4, 2026',
rows: [
{ label: 'Potassium', value: '5.9', valueSuffix: 'mmol/L', indicator: '↑', valueColor: '#a70002' /* --ahi-color-red-800 */ },
{ label: 'Sodium', value: '140', valueSuffix: 'mmol/L' },
{ label: 'Vitamin D', value: '28', valueSuffix: 'ng/mL', annotation: 'No flag received' },
{ label: 'Ferritin', value: '110', valueSuffix: 'ng/mL', annotation: 'No flag received' },
]
},
]
}]
});
}
};
</script>
Note what is not labelled. Sodium was assessed and came back normal, and it stays plain — most
results are normal, so an affirmative “Normal” on every in-range row would put a label on nearly every line
and make the flagged ones harder to find. Only the gaps are named. The wording is yours to choose: AKU
renders whatever string you send, so pick the one your clinicians recognize ('No flag received', 'No reference range') and use it consistently. Send no annotationColor for this
state — it is a gap in the data, not a severity, and coloring it would compete with the flags. Keep it short
where you can: a long label wraps rather than overflowing, but it takes the row with it, and the value
beside it always holds its width. One constraint on the wording: avoid “severe” and “life-threatening” in a
gap label. The printed view keyword-matches those words to style safety-critical allergy reactions, so a gap
label carrying either would print in bold as though the result itself were critical.
Use annotation and annotationColor on rows for severity or status labels — always
paired with visible text so color is never the sole indicator of meaning:
<ahi-care-summary id="grouped-allergy-demo" skeleton-count="1" max-groups="1" max-rows="1" style="--summary-columns:1;max-width:360px;"></ahi-care-summary>
<script>
document.querySelector('#grouped-allergy-demo').adapter = {
fetchSummary() {
return Promise.resolve({
boxes: [{
layout: 'grouped',
title: 'Active Allergies',
accentColor: '#b15a00' /* --ahi-color-orange-700 */,
groups: [
{
label: 'Penicillin',
critical: true,
rows: [
{ label: 'Anaphylaxis', annotation: 'Life-threatening', annotationColor: '#d00001' /* --ahi-color-red-700 */ },
{ label: 'Urticaria', annotation: 'Mild', annotationColor: '#677079' /* --ahi-color-gray-800 */ },
]
},
{
label: 'Sulfonamides',
rows: [
{ label: 'Stevens-Johnson syndrome', annotation: 'Severe', annotationColor: '#b15a00' /* --ahi-color-orange-700 */ },
]
},
{
label: 'Ibuprofen (NSAID class)',
rows: [
{ label: 'Angioedema', annotation: 'Severe', annotationColor: '#b15a00' /* --ahi-color-orange-700 */ },
{ label: 'Bronchospasm', annotation: 'Severe', annotationColor: '#b15a00' /* --ahi-color-orange-700 */ },
]
},
{
label: 'Codeine',
rows: [
{ label: 'Nausea, vomiting', annotation: 'Mild', annotationColor: '#677079' /* --ahi-color-gray-800 */ },
]
},
]
}]
});
}
};
</script>
The max-groups attribute limits how many non-critical groups are shown before the card clips
and fades its remaining groups. Clipped content is revealed by the summary’s unified
Show all control in the footer, which expands every clipped card at once (and collapses
them again). Groups marked critical: true are always visible — they render first and are
excluded from the hidden-item count — so safety-critical entries like life-threatening allergies are never
hidden when the card is collapsed. When max-groups is omitted or 0, all groups are
shown.
Set preserveGroupOrder: true on a grouped box to keep groups in the exact order
supplied and turn off the critical-group priority treatment: critical groups are no longer hoisted to the
top and are clipped by max-groups like any other group (they still keep their critical styling,
and the safety badges and print alert band are unaffected). Use this when a caller-defined order —
chronological lab history, a custom problem-list order — matters more than surfacing critical groups first.
Recording an absence
Clinical data often states an absence explicitly: “No Known Allergies”, “No Known Medications”. These arrive
as ordinary groups or tags, so without a signal the card header would count them as one item — a patient
with no allergies would show a count of 1.
Set negation: true on the group (or tag) that carries the assertion:
{
layout: 'grouped',
title: 'Active Allergies',
groups: [{ label: 'No Known Allergies', rows: [], negation: true }]
}
A negation entry:
- contributes 0 to the header count — a card containing nothing else shows no count badge at all, since the badge is hidden at zero
-
is excluded from the safety and review badges, and from the printed alert band, even if
criticalorwarningis also set — the absence of an allergy is not a safety-critical finding -
still renders exactly as it would otherwise, and still occupies a slot in
max-groups/max-tagstruncation, so the hidden-item count stays accurate
Set the flag from the data producer. The component never infers absence from label text — that would be
fragile and would break under localization. A group with an empty rows array but no
negation flag still counts as one item, since that is how a real allergy documented without a
coded reaction arrives.
Printing
Call summaryEl.print(context?) to open the browser print dialog. The component generates a
formatted clinical document in a hidden iframe and prints it — the host page is never modified.
The printed document always shows all content regardless of the max-rows,
max-tags, or max-groups limits set on the component. Tags with a
detail value have their detail text printed inline beneath the tag, so no interaction is needed
to see dosing instructions. Empty boxes are suppressed — they do not occupy space in the printed grid.
Each card header in the printed document mirrors the screen UI: the total item count is shown on the right,
and critical (red) or review (amber) safety badges appear when the box contains
critical-flagged allergy groups or warning-variant medication tags.
const printBtn = document.querySelector('#print-btn');
const summaryEl = document.querySelector('ahi-care-summary');
printBtn.addEventListener('click', () => {
summaryEl.print({
patientName: 'Margaret A. Thompson',
dob: '03/14/1951 (75 yrs)',
mrn: '0042-B',
room: '4W-12',
admissionDate: 'Nov 7, 2024',
facility: 'Heart & Vascular Center',
attendingPhysician: 'Dr. James Park, MD',
printedBy: 'Dr. Sarah Chen, MD',
footerNote: 'Generated from Azalea Health EHR — for clinical use only'
});
});
Print context fields
All fields on CareSummaryPrintContext are optional. Omit the entire argument to print without a
header.
| Field | Description |
|---|---|
patientName |
Patient’s full name — shown prominently at the top of the header. |
dob |
Date of birth in any display format (e.g. '03/14/1951 (75 yrs)'). |
mrn |
Medical record number. |
room |
Patient room or bed identifier (e.g. '4W-12'). |
admissionDate |
Admission or encounter date in any display format (e.g. 'Nov 7, 2024'). |
facility |
Facility or department name (e.g. 'Heart & Vascular Center'). |
attendingPhysician |
Attending or referring physician name. |
printedBy |
Clinician who triggered the print — appears in the bottom-right page margin alongside
printedAt (e.g. 'Dr. Sarah Chen, MD · 4/23/2026').
|
printedAt |
Override the timestamp shown in the page margin. Defaults to the current date/time in the user’s locale. |
footerNote |
Optional note in the bottom-left page margin on every page (e.g. a disclaimer or source system label). Omit to leave the bottom-left blank. |
transferMode |
When true, switches to the transfer/consultation layout. See
Transfer and consultation print below.
|
consultReason |
Pre-filled reason for the consultation or transfer. Only used when transferMode is
true.
|
Standard print (default)
The default print layout renders a two-column flow on letter paper (boxes fill the first column before
starting the second so the layout reads top-to-bottom rather than left-to-right). Patient name, MRN, DOB,
and room appear in the top page margin on continuation pages; printedBy and
printedAt appear in the bottom-right margin; footerNote appears in the bottom-left
margin. The first page suppresses the top margin boxes since the full letterhead header is already printed
there.
Before the grid, a severity-stratified alert band is injected automatically if the summary data contains any critical-flagged allergy groups or warning-variant medication tags. The alert band surfaces these items in a visually prominent strip above the grid so they are seen immediately — even when quickly flipping through a stack of printed summaries.
The alert band is derived entirely from the data already in the component and is split into two labeled sub-sections separated by a dashed rule:
-
Allergies & Safety — rows belonging to
groupedbox groups markedcritical: true; within this section, rows whoseannotationcontains “life-threatening” receive a filled-square marker (■) while others receive a smaller marker (▪) - Incomplete Medication Entries — tags with
warning: true
No additional configuration is needed — the band is present only when there is something to flag.
Transfer and consultation print
Set transferMode: true to switch to a single-column letterhead layout suitable for faxed
referrals and specialist handoff documents. The same @page margin boxes apply as in the
standard layout (patient name top-left, MRN/DOB/room top-right, footer note bottom-left, printed-by/at
bottom-right — suppressed on the first page). In this mode:
- All boxes flow in a single column for uninterrupted reading
- A “Reason for Consultation / Transfer” section appears prominently after the patient header
-
Supply
consultReasonto pre-fill this section with text; omit it to render three blank ruled lines for handwriting by the referring or receiving clinician - A signature block at the bottom provides four fields: referring clinician signature, referring date/time, receiving clinician signature, and receiving date/time
summaryEl.print({
patientName: 'Margaret A. Thompson',
dob: '03/14/1951 (75 yrs)',
mrn: '0042-B',
room: '4W-12',
admissionDate: 'Nov 7, 2024',
facility: 'Heart & Vascular Center',
attendingPhysician: 'Dr. James Park, MD',
printedBy: 'Dr. Sarah Chen, MD',
footerNote: 'Generated from Azalea Health EHR — for clinical use only',
transferMode: true,
consultReason:
'Elevated BNP (892 pg/mL) and new-onset reduced EF (35%) on echo with persistent atrial fibrillation — cardiology evaluation requested for GDMT optimization and ICD candidacy assessment.'
});
Both layouts include the severity-stratified alert band when critical or warning entries are present in the data.
Refreshing data
Call summaryEl.refresh() to reset all state and re-fetch summary data using the current
adapter. This is equivalent to re-assigning the adapter property but without replacing the
adapter object.
document.querySelector('#refresh-btn').addEventListener('click', () => {
document.querySelector('ahi-care-summary').refresh();
});
refresh() is a no-op when no adapter is set. If the server returns HTTP 202 after a refresh,
the component resumes polling normally until data resolves.
Skeleton loading
While the adapter is polling, the component renders placeholder skeleton boxes. Set
skeleton-count to match the number of boxes your adapter will eventually return so the layout
does not shift when data arrives:
<ahi-care-summary skeleton-count="4"></ahi-care-summary>
Responsive layout
The grid uses CSS container queries to adapt to available width. By default the grid renders with 4 columns,
collapsing to 2 columns when the component is narrower than 900 px and to a single column below 540 px. Use
the --summary-columns CSS custom property to override the default column count for wider
layouts:
ahi-care-summary {
--summary-columns: 3;
}
Best practices
Care summary should:
-
Always provide a
CareSummaryDataAdapter, even for static or mock data, so the data-loading contract stays consistent across environments. -
Throw
CareSummaryPendingErrorfrom the adapter for HTTP 202 responses — this keeps the component in its loading state and continues polling rather than showing an error. -
Set
skeleton-countto match the expected number of boxes to prevent layout shift on load. -
Set
accentColorconsistently across your application — use the same color for the same clinical domain (e.g. always red for problems, always teal for labs). -
Use
valueColorandindicatoronkey-valuefields to draw attention to flagged or abnormal values, not decoratively. -
Use
groupedwhen data has a parent–child structure: allergies (substance → reactions with severity), problems (body system → diagnoses with status), lab results (panel → flagged values). Each group row supports both value display (value,valueSuffix,indicator,valueColor) and annotation display (annotation,annotationColor) — independently or together. -
Always supply
annotationtext alongsideannotationColorin grouped rows — color alone is not sufficient for accessibility. -
Label the gaps in lab data, not the normals. A result that came back with no interpretation reads as
reassuring unless something says otherwise; send an
annotation(e.g.'No flag received') beside its value. Treat an interpretation code you do not recognize the same way — as un-interpreted rather than as normal, since it is a gap in the data either way. Leave assessed-normal rows unmarked so the flagged ones keep their prominence. -
Mark groups
critical: truewhen they must never be hidden bymax-groupstruncation — e.g. life-threatening allergies. Critical groups are always rendered first and are excluded from the hidden-item count. To keep a fixed source order instead (and clip critical groups like any other), setpreserveGroupOrder: trueon the box. -
Use
tagswithdetailfor supplemental on-demand information (e.g. SIG directions) that would clutter the default view but is still clinically useful. -
Use
warning: trueon tags for medication entries missing safety-critical data such as dose or frequency. Do not rely oncoloralone — thewarningflag provides an icon, dashed border, and accessible label signal in addition to any color you supply. -
Set
negation: trueon any group or tag that asserts an absence (“No Known Allergies”, “No Known Medications”) so it is left out of the header count and the safety badges. See Recording an absence. -
Set
max-rowsto keepkey-valueboxes compact. Setmax-tagsto keeptagsboxes compact —warning: truetags are always exempt from truncation and always sort first. Setmax-groupsto keepgroupedboxes compact — mark safety-critical groups withcritical: trueso they are never hidden.
Care summary should NOT:
- Return large amounts of data from the adapter — boxes are intended to surface a handful of the most clinically relevant items, not exhaustive lists.
- Be used for longitudinal event history — use
ahi-care-timelinefor that. -
Throw errors from the adapter for transient not-ready states — use
CareSummaryPendingErrorso the component continues polling.
Importing
To import this component using a script tag:
<script type="module" src="/path/to/azalea-kit-ui/dist/components/care-summary/care-summary.js"></script>
To import this component using a bundler:
import '@azaleahealth/azalea-kit-ui/dist/components/care-summary/care-summary.js';
Properties
| Name | Description | Type | Default | |
|---|---|---|---|---|
adapter
|
The data adapter that supplies summary data. Setting this property resets all state and starts a fresh polling cycle. |
CareSummaryDataAdapter | null
|
null
|
|
errors
|
Consumer-supplied errors to display in the component’s error region — use this to surface failures
that occurred while fetching data outside the adapter (e.g. one of several upstream sources failed).
Each entry’s
message is shown to the user; when details is present it is revealed
through a collapsed-by-default disclosure. Errors thrown by the adapter are displayed in the same
region automatically. Setting [] or null
removes consumer-supplied entries only.
|
CareError[] | null
|
null
|
|
skeletonCount
skeleton-count
|
Number of skeleton placeholder boxes to render while loading. Should match the expected number of
summary boxes for the best UX. Defaults to 4.
|
number
|
4
|
|
pollInterval
poll-interval
|
Polling interval in milliseconds. The component waits this long between
fetchSummary calls while the server returns a pending (202) response. Defaults to
3000 (3 s).
|
number
|
3000
|
|
maxTags
max-tags
|
Maximum number of non-warning tags to show in a 'tags' layout box before clipping.
Warning tags are always visible regardless of this limit. Set to 0 to show all tags.
Defaults to 0 (no limit).
|
number
|
0
|
|
maxRows
max-rows
|
Maximum number of rows to show in a 'key-value' layout box before clipping. Set to
0 to show all rows. Defaults to 0 (no limit).
|
number
|
0
|
|
maxGroups
max-groups
|
Maximum number of non-critical groups to show in a 'grouped' layout box before
clipping. Groups marked critical: true are always visible regardless of this limit —
unless the box sets preserveGroupOrder, in which case critical groups are clipped like
any other group. 0 shows all groups (default).
|
number
|
0
|
|
headingLevel
heading-level
|
Heading level (1–6) exposed to assistive technology for each card’s title, so screen-reader users
can navigate between cards by heading. Set it to fit the host page’s outline (e.g.
3 when the component sits under an <h2> section heading). Defaults
to 3.
|
number
|
3
|
|
updateComplete |
A read-only promise that resolves when the component has finished updating. |
Learn more about properties and attributes.
Events
| Name | Description | Event Detail |
|---|---|---|
ahi-care-summary-load |
Emitted once when the adapter resolves with data. detail.data is the
CareSummaryData payload.
|
AhiCareSummaryLoadEvent
|
ahi-care-summary-error |
Emitted when the adapter throws a non-pending error. detail.error is the thrown
Error.
|
AhiCareSummaryErrorEvent
|
Learn more about events.
Methods
| Name | Description | Arguments |
|---|---|---|
print() |
Opens the browser print dialog for a formatted clinical summary document. The printed document
always shows all content regardless of the max-rows,
max-tags, or max-groups limits set on the component. Tags with a
detail property have their detail text printed inline beneath the tag, so no
interaction is required to see dosing instructions. Call this from a print button in your
application — the component does not render the button itself.
|
context: CareSummaryPrintContext
|
refresh() |
Resets all state and re-fetches summary data using the current adapter. No-op if no adapter is set. | - |
Learn more about methods.
Custom Properties
| Name | Description | Default |
|---|---|---|
--summary-columns |
Number of columns in the grid. Overridden by the component’s built-in responsive breakpoints on narrow viewports. | 4 |
--summary-gap |
Gap between summary boxes. | var(–ahi-space-4) |
--summary-grid-max-height |
Maximum height of the card grid. When set, only the grid scrolls internally and the unified footer remains fully visible below it. Use this instead of capping the component’s own height so the footer is never clipped. | none |
--summary-clip-height |
Maximum height applied to an overflowing card’s body before it is clipped and faded. Cards without overflow are unaffected. | 320px |
Learn more about customizing CSS custom properties.
CSS Parts
| Name | Description |
|---|---|
base |
Deprecated. Use the care-summary part instead. |
care-summary |
The component’s outer wrapper. |
errors |
The error region listing adapter and consumer-supplied data-fetch errors. |
grid |
The summary box grid container. |
Learn more about customizing CSS parts.
Dependencies
This component automatically imports the following dependencies.
<ahi-icon>