Skip to content

cv-card

Visual container that groups related content into a cohesive unit, with an optional expandable variant that follows the disclosure pattern to show/hide body content.

Headless: createCard

Usage

View source
html
<div class="card-demo-shell" data-demo="card">
  <section class="card-demo-hero" aria-labelledby="card-demo-title">
    <div class="card-demo-copy">
      <span class="card-demo-kicker">Container primitive</span>
      <h3 id="card-demo-title">
        Use card to bind media, state, body copy, and actions into one scannable unit.
      </h3>
      <p>
        The component owns slot layout and disclosure contracts. Product surfaces can tune density and color
        through custom properties without moving behavior out of the headless model.
      </p>
    </div>

    <dl class="card-demo-metrics" aria-label="Card contract summary">
      <div>
        <dt>Variants</dt>
        <dd>elevated / outlined / filled</dd>
      </div>
      <div>
        <dt>Slots</dt>
        <dd>image, header, body, footer</dd>
      </div>
      <div>
        <dt>Events</dt>
        <dd>cv-input + cv-change</dd>
      </div>
    </dl>
  </section>

  <section class="card-demo-board" aria-label="Card variants and states">
    <cv-card class="card-demo-main-card" variant="elevated">
      <img slot="image" src="../images/card-preview.png" alt="Faceted encrypted workspace preview" />
      <div slot="header" class="card-demo-card-head">
        <span>Border vault review</span>
        <cv-badge variant="success" size="small">verified</cv-badge>
      </div>

      <div class="card-demo-body">
        <p>
          Visible profile audit completed. Deniable namespaces stay outside this surface until the operator
          enters a matching threat model.
        </p>

        <div class="card-demo-proof-grid" aria-label="Review details">
          <span>
            <strong>3</strong>
            exposed entries
          </span>
          <span>
            <strong>0</strong>
            integrity warnings
          </span>
          <span>
            <strong>2</strong>
            paired devices
          </span>
        </div>
      </div>

      <div slot="footer" class="card-demo-actions">
        <cv-button variant="primary" size="small">Open review</cv-button>
        <cv-button variant="ghost" size="small" outline>Export proof</cv-button>
      </div>
    </cv-card>

    <div class="card-demo-side" aria-label="Compact card variants">
      <cv-card variant="outlined" class="card-demo-compact-card">
        <div slot="header" class="card-demo-card-head">
          <span>Relay boundary</span>
          <cv-badge variant="primary" size="small">active</cv-badge>
        </div>
        <p>Outlined cards keep a quiet surface while still separating trust-boundary metadata.</p>
      </cv-card>

      <cv-card variant="filled" class="card-demo-compact-card">
        <div slot="header" class="card-demo-card-head">
          <span>Sync window</span>
          <cv-badge variant="warning" size="small">18 min</cv-badge>
        </div>
        <p>Filled cards work for secondary status where elevation would overstate priority.</p>
      </cv-card>

      <cv-card expandable class="card-demo-disclosure-card" variant="outlined">
        <div slot="header" class="card-demo-card-head">
          <span>Compatibility disclosure</span>
          <cv-badge variant="neutral" size="small">legacy</cv-badge>
        </div>
        <p>
          Expandable cards remain supported for existing flows. For new single-section reveal UI, prefer
          <code>cv-disclosure</code>.
        </p>
      </cv-card>
    </div>
  </section>
</div>

Anatomy

<cv-card> (host)
└── <div part="base">
    ├── <div part="image">
    │   └── <slot name="image">
    ├── <div part="header">          ← trigger when [expandable]
    │   ├── <slot name="header">
    │   └── <span part="indicator">  ← only when [expandable], chevron/arrow
    ├── <div part="body">            ← collapsible when [expandable]
    │   └── <slot>
    └── <div part="footer">
        └── <slot name="footer">

Attributes

AttributeTypeDefaultDescription
variantString"elevated"Visual variant: elevated | outlined | filled
expandableBooleanfalseEnables disclosure behavior (expand/collapse body)
expandedBooleanfalseExpanded state (only meaningful when expandable is true)
disabledBooleanfalsePrevents interaction (only meaningful when expandable is true)

Variants

VariantDescription
elevatedDefault style with box shadow for a raised appearance
outlinedTransparent background with a visible border
filledSolid surface background without shadow or prominent border

Variants are CSS-only concerns and carry no headless state. They are mutually exclusive and selected via the variant attribute.

Slots

SlotDescription
imageOptional image or media displayed at the top of the card
headerCard header content (title, subtitle, actions)
(default)Main body content of the card
footerCard footer content (actions, metadata)

Direct slotted flow text (p, ul, ol) in the body and footer inherits the card section typography and has block margins reset so card spacing controls the vertical rhythm.

CSS Parts

PartElementDescription
base<div>Root wrapper element
image<div>Container for the image slot
header<div>Header area; acts as the disclosure trigger when expandable is true
indicator<span>Expand/collapse indicator icon; rendered only when expandable is true
body<div>Body content area; collapsible region when expandable is true
footer<div>Footer area

CSS Custom Properties

PropertyDefaultDescription
--cv-card-paddingvar(--cv-space-4, 16px)Inner padding for card sections (header, body, footer)
--cv-card-border-radiusvar(--cv-radius-md, 8px)Border radius of the card
--cv-card-border-colorvar(--cv-color-border, #2a3245)Border color (primarily for outlined variant)
--cv-card-backgroundvar(--cv-color-surface, #141923)Background color of the card
--cv-card-shadow0 1px 3px rgba(0, 0, 0, 0.24)Box shadow (primarily for elevated variant)
--cv-card-gapvar(--cv-space-0, 0px)Spacing between card sections
--cv-card-media-aspect-ratio16 / 9Aspect ratio for slotted image media
--cv-card-header-colorvar(--cv-color-text-strong, #f5f7fc)Header text color
--cv-card-header-font-familyvar(--cv-font-family-display, var(--cv-font-family-body, inherit))Header font family
--cv-card-header-font-sizevar(--cv-font-size-md, 1rem)Header text size
--cv-card-header-font-weightvar(--cv-font-weight-semibold, 600)Header text weight
--cv-card-header-line-height1.25Header line height
--cv-card-body-colorvar(--cv-color-text-muted, #9aa6bf)Body text color
--cv-card-body-font-sizevar(--cv-font-size-sm, 0.875rem)Body text size
--cv-card-body-line-height1.6Body line height
--cv-card-footer-colorvar(--cv-color-text-secondary, var(--cv-color-text-muted, #9aa6bf))Footer text color
--cv-card-footer-font-sizevar(--cv-font-size-sm, 0.875rem)Footer text size
--cv-card-footer-line-height1.35Footer line height
--cv-card-indicator-sizevar(--cv-space-4, 16px)Size of the expand/collapse indicator icon
--cv-card-indicator-transitionvar(--cv-duration-fast, 120ms) var(--cv-easing-standard, ease)Transition for indicator rotation

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

Theme PropertyDefaultDescription
--cv-color-border#2a3245Base border color
--cv-color-surface#141923Surface background color
--cv-color-text#e8ecf6Default text color
--cv-duration-fast120msTransition duration
--cv-easing-standardeaseTransition timing function
--cv-radius-md8pxBase radius used for card fallback
--cv-space-00pxZero spacing scale fallback
--cv-space-416pxLarge spacing scale fallback

Visual States

Host selectorDescription
:host([variant="elevated"])Applies box shadow via --cv-card-shadow; no visible border
:host([variant="outlined"])Applies visible border via --cv-card-border-color; no shadow
:host([variant="filled"])Solid background; no shadow, subtle or no border
:host([expandable])Renders indicator in header; header becomes interactive trigger
:host([expanded])Body content visible; indicator rotated to open position
:host([disabled])Reduced opacity (0.55), cursor: not-allowed on trigger; interaction blocked

Reactive State Mapping

cv-card is a visual adapter over headless createCard.

UIKit Property -> Headless Binding (input direction)

UIKit PropertyDirectionHeadless Binding
expandableattr -> optionpassed as isExpandable in createCard(options)
expandedattr -> optionpassed as isExpanded in createCard(options); updates via actions.toggle() / actions.expand() / actions.collapse()
disabledattr -> actionactions.setDisabled(value)

Headless State -> DOM Reflection (output direction)

Headless StateDirectionDOM Reflection
state.isExpandable()state -> attr[expandable] host attribute
state.isExpanded()state -> attr[expanded] host attribute
state.isDisabled()state -> attr[disabled] host attribute

Contract Spreading

ContractTarget ElementNotes
contracts.getCardProps()[part="base"]Spread as attributes on the card root element
contracts.getTriggerProps()[part="header"]Spread onto the header element when isExpandable is true; provides role="button", aria-expanded, aria-controls, tabindex, and event handlers
contracts.getContentProps()[part="body"]Spread onto the body element when isExpandable is true; provides id, role="region", aria-labelledby, hidden

Boundary

  • UIKit dispatches cv-input and cv-change events by observing isExpanded changes triggered by user activation (not by controlled attribute updates).
  • UIKit does not own toggle, expand/collapse, or keyboard logic; headless state is the source of truth.
  • Visual variants (elevated, outlined, filled) are CSS-only; no headless state involved.
  • Slot layout, image positioning, indicator rendering, and CSS transitions are UIKit rendering concerns.

Events

EventDetailDescription
cv-input{expanded: boolean}Fires when user toggles the expanded state (click or keyboard on trigger)
cv-change{expanded: boolean}Fires when the expanded state commits after user interaction

Events only fire when expandable is true and the state change is triggered by user action (not programmatic attribute changes). Normal (non-expandable) cards do not emit these events.

Disclosure Guidance

Use cv-card for static grouped content. For a single reveal/hide interaction, prefer cv-disclosure; for multiple related sections, prefer cv-accordion. cv-card expandable remains compatible for existing surfaces, but it is not the preferred pattern for new disclosure UI.

View source
html
<cv-card expandable expanded>
  <span slot="header">Compatibility disclosure</span>
  <p>This fallback keeps existing expandable cards working while new flows use disclosure primitives.</p>
</cv-card>

ChromVoid UIKit documentation