Skip to content

cv-callout

Static supplementary content block that highlights important information using role="note". Unlike cv-alert, a callout is not a live region and does not announce dynamically.

Headless: createCallout

Usage

View source
html
<div class="callout-demo-shell" data-demo="callout" data-live-demo-height="640">
  <section class="callout-demo-hero" aria-labelledby="callout-demo-title">
    <div class="callout-demo-copy">
      <span class="callout-demo-kicker">Static guidance surface</span>
      <h3 id="callout-demo-title">Use callout for durable context, not time-sensitive alerts.</h3>
      <p>
        A callout renders as <code>role="note"</code>, keeps all ARIA and close behavior in headless state,
        and can be styled by semantic variant without becoming a live region.
      </p>
    </div>

    <dl class="callout-demo-metrics" aria-label="Callout contract summary">
      <div>
        <dt>Root role</dt>
        <dd>note</dd>
      </div>
      <div>
        <dt>Announcements</dt>
        <dd>not live</dd>
      </div>
      <div>
        <dt>Close event</dt>
        <dd>cv-close</dd>
      </div>
    </dl>
  </section>

  <section class="callout-demo-workbench" aria-labelledby="callout-demo-workbench-title">
    <div class="callout-demo-section-header">
      <span class="callout-demo-kicker">Vault review panel</span>
      <h4 id="callout-demo-workbench-title">
        Layer durable notes around a workflow without stealing focus or announcing new status.
      </h4>
    </div>

    <div class="callout-demo-layout">
      <div class="callout-demo-stack" aria-label="Callout examples in context">
        <cv-callout variant="info">
          <span slot="icon" class="callout-demo-icon">i</span>
          Visible profile is safe to inspect. Hidden namespaces are not listed in this surface.
        </cv-callout>

        <cv-callout variant="success">
          <span slot="icon" class="callout-demo-icon">OK</span>
          USB relay trust boundary verified against the local device key.
        </cv-callout>

        <cv-callout variant="warning" closable data-demo-label="Recovery window">
          <span slot="icon" class="callout-demo-icon">!</span>
          Recovery window closes in 18 minutes. Dismiss only after the operator has acknowledged it.
        </cv-callout>

        <cv-callout variant="danger" closable data-demo-label="Export block">
          <span slot="icon" class="callout-demo-icon">X</span>
          Full export is blocked while a coercion profile is active.
        </cv-callout>
      </div>

      <aside class="callout-demo-side" aria-label="Callout variants and density">
        <div class="callout-demo-chip-row" aria-label="Available variants">
          <cv-badge variant="primary" size="small">info</cv-badge>
          <cv-badge variant="success" size="small">success</cv-badge>
          <cv-badge variant="warning" size="small">warning</cv-badge>
          <cv-badge variant="danger" size="small">danger</cv-badge>
          <cv-badge variant="neutral" size="small">neutral</cv-badge>
        </div>

        <cv-callout variant="neutral" density="compact">
          <span slot="icon" class="callout-demo-icon">N</span>
          Compact neutral note for route-level context.
        </cv-callout>

        <cv-callout variant="info" density="dense">
          <span slot="icon" class="callout-demo-icon">D</span>
          Dense inline note for secondary metadata.
        </cv-callout>

        <output class="callout-demo-readout" aria-live="polite" data-callout-readout>
          Dismissible notes are open. Close one to see the cv-close event.
        </output>

        <button type="button" class="callout-demo-restore" data-callout-restore>
          Restore dismissed notes
        </button>
      </aside>
    </div>
  </section>
</div>

<script>
  document.querySelectorAll('.callout-demo-shell[data-demo="callout"]:not([data-ready])').forEach((shell) => {
    shell.dataset.ready = 'true'

    const readout = shell.querySelector('[data-callout-readout]')
    const restore = shell.querySelector('[data-callout-restore]')
    const closableNotes = Array.from(shell.querySelectorAll('cv-callout[closable]'))

    const syncDismissedCount = (label) => {
      const dismissed = closableNotes.filter((note) => note.open === false).length
      shell.dataset.dismissed = String(dismissed)
      if (readout) {
        readout.textContent =
          dismissed === 0
            ? 'Dismissible notes are open. Close one to see the cv-close event.'
            : `cv-close from ${label}. ${dismissed} dismissible note${dismissed === 1 ? '' : 's'} hidden.`
      }
    }

    shell.addEventListener('cv-close', (event) => {
      const note = event.target instanceof HTMLElement ? event.target.closest('cv-callout') : null
      syncDismissedCount(note?.dataset.demoLabel || 'callout')
    })

    restore?.addEventListener('click', () => {
      closableNotes.forEach((note) => {
        note.open = true
      })
      syncDismissedCount('restore')
    })
  })
</script>

Anatomy

<cv-callout> (host)
└── <div part="base" role="note">
    ├── <span part="icon">
    │   └── <slot name="icon">
    ├── <span part="message">
    │   └── <slot>
    └── <button part="close-button" aria-label="Dismiss">   ← only when [closable]

Attributes

AttributeTypeDefaultDescription
variantString"info"Visual variant: "info" | "success" | "warning" | "danger" | "neutral"
densityStringDensity preset: compact | dense
closableBooleanfalseRenders a dismiss button and enables the cv-close event
openBooleantrueControls visibility of the callout

Variants

VariantDescription
infoDefault informational style using --cv-color-info
successSuccess-tinted background and border using --cv-color-success
warningWarning-tinted background and border using --cv-color-warning
dangerDanger-tinted background and border using --cv-color-danger
neutralMuted style with surface background and border

Densities

DensityDescription
compactReduced padding/radius for route-level callouts
denseSmallest density for status and inline callouts

Slots

SlotDescription
(default)Main content (projected into the message area)
iconLeading icon area before the message content

CSS Parts

PartElementDescription
base<div>Outer container with role="note"; receives headless getCalloutProps() attributes
icon<span>Wrapper around the icon slot
message<span>Wrapper around the default slot
close-button<button>Dismiss button (rendered only when closable is true)

CSS Custom Properties

PropertyDefaultDescription
--cv-callout-padding-inlinevar(--cv-space-3, 12px)Horizontal padding
--cv-callout-padding-blockvar(--cv-space-3, 12px)Vertical padding
--cv-callout-gapvar(--cv-space-2, 8px)Gap between icon, message, and close button
--cv-callout-border-radiusvar(--cv-radius-sm, 6px)Border radius
--cv-callout-border-colorvar(--cv-color-border, #2a3245)Border color (overridden per variant)
--cv-callout-backgroundvar(--cv-color-surface-elevated, #1d2432)Background color (overridden per variant)
--cv-callout-colorvar(--cv-color-text, #e8ecf6)Text color
--cv-callout-icon-colorcurrentColorIcon slot color (overridden per variant)
--cv-callout-font-sizevar(--cv-font-size-base, 14px)Font size of callout content
--cv-callout-transition-durationvar(--cv-duration-fast, 120ms)Transition duration for show/hide
--cv-callout-transition-easingvar(--cv-easing-standard, ease)Transition timing function

Additionally, component styles depend on theme tokens through fallback values:

Theme PropertyDefaultDescription
--cv-color-border#2a3245Base border color
--cv-color-surface-elevated#1d2432Elevated surface background color
--cv-color-text#e8ecf6Default text color
--cv-color-info#65d7ffInfo accent color
--cv-color-success#5beba0Success accent color
--cv-color-warning#ffc857Warning accent color
--cv-color-danger#ff7d86Danger accent color
--cv-duration-fast120msTransition duration
--cv-easing-standardeaseTransition timing function
--cv-radius-sm6pxBase radius fallback
--cv-space-28pxMedium spacing scale fallback
--cv-space-312pxMedium-large spacing scale fallback

Visual States

Host selectorDescription
:host([open])Visible state; callout is rendered and visible
:host(:not([open]))Hidden state; callout is hidden (e.g., display: none or fade-out transition)
:host([variant="info"])Info-tinted background and border (default)
:host([variant="success"])Success-tinted background and border
:host([variant="warning"])Warning-tinted background and border
:host([variant="danger"])Danger-tinted background and border
:host([variant="neutral"])Muted surface background with border
:host([closable])Close button is rendered in the template

Reactive State Mapping

cv-callout is a visual adapter over headless createCallout.

UIKit PropertyDirectionHeadless Binding
variantattr -> actionactions.setVariant(value)
closableattr -> actionactions.setClosable(value)
openattr -> actionactions.show() / programmatic visibility control
Headless StateDirectionDOM Reflection
state.variant()state -> attr[variant] host attribute
state.closable()state -> attr[closable] host attribute
state.open()state -> attr[open] host attribute
  • contracts.getCalloutProps() is spread onto the inner [part="base"] element to apply id, role="note", and data-variant.
  • contracts.getCloseButtonProps() is spread onto the inner [part="close-button"] element to apply id, role="button", tabindex="0", aria-label="Dismiss", and onClick handler. The close button is only rendered when closable is true.
  • UIKit dispatches cv-close when the headless actions.close() transitions open from true to false.
  • UIKit does not own ARIA logic or close behavior; headless state is the source of truth.
  • The callout root is not focusable; no tabindex is applied to the root element.

Events

EventDetailDescription
cv-closeundefinedFires when the callout is dismissed via the close button (closable must be true)

No cv-input or cv-change events are emitted. The callout has no user-modifiable state; closing is a one-way action.

ChromVoid UIKit documentation