Changelog Azalea Kit UI 101 Usage Customizing Utilities Form controls Laravel Vue FHIR Types Accessibility Icons Actions Button Button group Layout and structure Card Card section Care summary Care timeline Divider Empty state Footer Header Layout Stack Table Selection and input Calendar Checkbox Checkbox group Color picker Combobox Combobox option Input Input mask Memorable date Number input Option Phone input Radio Radio button Radio group Range Select SSN input Tag Textarea Toggle ZIP code input Images and icons Avatar Icon Icon button Logo Feedback indicators Badge Banner Progress bar Skeleton Skeleton body text Skeleton display text Skeleton thumbnail Spinner Typography Text Navigation Breadcrumb Breadcrumb item Dropdown Menu Menu item Menu label Navigation Nav group Nav item Nav toggle Pagination Tab Tab group Tab panel Top bar Tree Tree item User menu Overlays Dialog Popover Tooltip Utilities Collapsible FHIR address FHIR human name FHIR quantity FHIR questionnaire Format bytes Format date Format number Relative time Resize observer Split panel Tooltip Border Color Shadow Spacing Transition Typography Z-index Contributing Visual Tests Light Dark System

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>
Open in new window Unified Patient Clinical View Example

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 a detail string 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 to true to flag an entry that has missing safety-critical data. Warning tags always appear first in the list and are never hidden by max-tags truncation. 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:

  1. An alert-triangle icon always visible inside the pill
  2. An amber dashed border around the pill
  3. 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, and valueColor. Use for lab results, measurements, and numeric clinical data.
  • Annotation mode — a right-aligned text label with a colored left-border accent, controlled by annotation and annotationColor. 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 critical or warning is 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-tags truncation, 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'
  });
});

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 grouped box groups marked critical: true; within this section, rows whose annotation contains “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 consultReason to 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 CareSummaryPendingError from the adapter for HTTP 202 responses — this keeps the component in its loading state and continues polling rather than showing an error.
  • Set skeleton-count to match the expected number of boxes to prevent layout shift on load.
  • Set accentColor consistently across your application — use the same color for the same clinical domain (e.g. always red for problems, always teal for labs).
  • Use valueColor and indicator on key-value fields to draw attention to flagged or abnormal values, not decoratively.
  • Use grouped when 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 annotation text alongside annotationColor in 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: true when they must never be hidden by max-groups truncation — 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), set preserveGroupOrder: true on the box.
  • Use tags with detail for supplemental on-demand information (e.g. SIG directions) that would clutter the default view but is still clinically useful.
  • Use warning: true on tags for medication entries missing safety-critical data such as dose or frequency. Do not rely on color alone — the warning flag provides an icon, dashed border, and accessible label signal in addition to any color you supply.
  • Set negation: true on 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-rows to keep key-value boxes compact. Set max-tags to keep tags boxes compact — warning: true tags are always exempt from truncation and always sort first. Set max-groups to keep grouped boxes compact — mark safety-critical groups with critical: true so 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-timeline for that.
  • Throw errors from the adapter for transient not-ready states — use CareSummaryPendingError so the component continues polling.

Importing

Script Bundler

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>