page Cardcomponent { seo { title = "Card component" description = "Theme-aware, responsive card component." } view {
W WRNexusJS
base component

Card

Theme-aware, responsive card component.

@wrnexus/ui 0.5.031 props1 slots6 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<Card
  title="Card title"
  subtitle="subtitle"
  description="description"
  header="header"
  footer="footer"
/>

Legacy mount name: data-component="Card".

Props and attributes

PropTypeRequirementDefault
titlestringOptional"Card title"
subtitlestringOptional""
descriptionstringOptional""
headerstringOptional""
footerstringOptional""
imageSrcstringOptional""
imageAltstringOptional""
imagePositionstringOptional"top"
actionLabelstringOptional""
actionHrefstringOptional""
headerActionsstringOptional[]
navigationstringOptional[]
activeNavstringOptional""
mobileNavigationbooleanOptionalfalse
alertTitlestringOptional""
alertDescriptionstringOptional""
emptybooleanOptionalfalse
emptyTitlestringOptional"No data to show"
emptyIconstringOptional"icon-[lucide--inbox]"
itemsstringOptional[]
sizestringOptional"md"
colorstringOptional"primary"
variantstringOptional"default"
layoutstringOptional"vertical"
alignstringOptional"left"
hoverstringOptional"none"
scrollablebooleanOptionalfalse
maxHeightstringOptional"18rem"
dismissiblebooleanOptionalfalse
ariaLabelstringOptional""
classstringOptional""

Slots

  • default

Events

  • click
  • action
  • navigate
  • dismiss
  • load
  • error

Source contract

Installed source: components/card.wrn

component Card {
  props {
    @event click = function
    @event action = function
    @event navigate = function
    @event dismiss = function
    @event load = function
    @event error = function
    title = "Card title"
    subtitle = ""
    description = ""
    header = ""
    footer = ""
    imageSrc = ""
    imageAlt = ""
    imagePosition = "top"
    actionLabel = ""
    actionHref = ""
    headerActions = []
    navigation = []
    activeNav = ""
    mobileNavigation = false
    alertTitle = ""
    alertDescription = ""
    empty = false
    emptyTitle = "No data to show"
    emptyIcon = "icon-[lucide--inbox]"
    items = []
    size = "md"
    color = "primary"
    variant = "default"
    layout = "vertical"
    align = "left"
    hover = "none"
    scrollable = false
    maxHeight = "18rem"
    dismissible = false
    ariaLabel = ""
    class = ""
  }

  state visible = true
  state selectedNav = activeNav

  functions {
    function chooseNavigation(item, index) {
      previousValue = selectedNav
      selectedNav = item.value || item.label
      $emit("navigate", {
        value: selectedNav,
        previousValue: previousValue,
        item: item,
        index: index
      })
    }

    function chooseMobileNavigation(sourceEvent) {
      previousValue = selectedNav
      selectedNav = sourceEvent.target.value
      $emit("navigate", {
        value: selectedNav,
        previousValue: previousValue
      })
    }

    function chooseAction(action, index) {
      $emit("action", {
        action: action,
        index: index
      })
    }

    function dismissCard(sourceEvent) {
      panel = sourceEvent.currentTarget.closest(".wire-next__card-panel")
      if (panel) {
        panel.setAttribute("hidden", "")
      }
      visible = false
      $emit("dismiss", {
        title: title
      })
    }
  }

  view {
        <section
      {...attrs}
      class="wire-next wire-next--card {class}"
      data-size="{size}"
      data-color="{color}"
      data-variant="{variant}"
      data-layout="{layout}"
      data-align="{align}"
      data-hover="{hover}"
      data-image-position="{imagePosition}"
      data-group="{items.length > 0}"
      aria-label="{ariaLabel || title}"
    >
      {#if items.length > 0}
        <div class="wire-next__card-group">
          {#each items as item}
            <article class="wire-next__card-panel">
              {#if item.imageSrc}
                <div class="wire-next__card-media">
        <img src="{item.imageSrc}" alt="{item.imageAlt || ''}" loading="lazy" />
        </div>
              {/if}
              <div class="wire-next__card-body">
                {#if item.title}<h3>{item.title}</h3>{/if}
                {#if item.description}<p>{item.description}</p>{/if}
              </div>
              {#if item.footer}<footer class="wire-next__card-footer">{item.footer}</footer>{/if}
            </article>
          {/each}
        </div>
      {:else if visible}
        <article class="wire-next__card-panel">
          {#if imagePosition === "overlay" && imageSrc}
            <div class="wire-next__card-media wire-next__card-media--overlay">
        <img
                src="{imageSrc}"
                alt="{imageAlt}"
                loading="lazy"
                @load="$emit('load', { src: imageSrc })"
                @error="$emit('error', { src: imageSrc })"
              />
        <div class="wire-next__card-overlay">
                {#if title}<h3>{title}</h3>{/if}
                {#if subtitle}<span class="wire-next__card-subtitle">{subtitle}</span>{/if}
                {#if description}<p>{description}</p>{/if}
                {#if footer}<small>{footer}</small>{/if}
              </div>
        </div>
          {:else}
            {#if header || headerActions.length > 0 || dismissible}
              <header class="wire-next__card-header">
        <span>{header}</span>
                {#if headerActions.length > 0 || dismissible}
                  <span class="wire-next__card-header-actions">
                    {#each headerActions as action, index}
                      <button
                        type="button"
                        aria-label="{action.ariaLabel || action.label}"
                        title="{action.label}"
                        @click="chooseAction(action, index)"
                      >
                        {#if action.icon}<span class="{action.icon}" aria-hidden="true">
        </span>{/if}
                        {#if !action.icon}<span>{action.label}</span>{/if}
                      </button>
                    {/each}
                    {#if dismissible}
                      <button type="button" aria-label="Close card" @click="dismissCard(event)">
        <span class="icon-[lucide--x]" aria-hidden="true">
        </span>
        </button>
                    {/if}
                  </span>
                {/if}
              </header>
            {/if}

            {#if navigation.length > 0}
              <nav class="wire-next__card-navigation" aria-label="{ariaLabel || title}">
        <div class="wire-next__card-tabs">
                  {#each navigation as item, index}
                    <button
                      type="button"
                      data-active="{selectedNav === (item.value || item.label)}"
                      aria-current="{selectedNav === (item.value || item.label) ? 'page' : ''}"
                      @click="chooseNavigation(item, index)"
                    >
                      {item.label}
                    </button>
                  {/each}
                </div>
                {#if mobileNavigation}
                  <select
                    class="wire-next__card-navigation-select"
                    aria-label="{ariaLabel || title}"
                    value="{selectedNav}"
                    @change="chooseMobileNavigation(event)"
                  >
                    {#each navigation as item}
                      <option
                        value="{item.value || item.label}"
                        selected="{selectedNav === (item.value || item.label)}"
                      >
                        {item.label}
                      </option>
                    {/each}
                  </select>
                {/if}
              </nav>
            {/if}

            {#if imageSrc && imagePosition !== "bottom"}
              <div class="wire-next__card-media">
        <img
                  src="{imageSrc}"
                  alt="{imageAlt}"
                  loading="lazy"
                  @load="$emit('load', { src: imageSrc })"
                  @error="$emit('error', { src: imageSrc })"
                />
        </div>
            {/if}

            {#if alertTitle || alertDescription}
              <aside class="wire-next__card-alert" role="status">
                {#if alertTitle}<strong>{alertTitle}</strong>{/if}
                {#if alertDescription}<span>{alertDescription}</span>{/if}
              </aside>
            {/if}

            <div
              class="wire-next__card-body"
              data-scrollable="{scrollable}"
              style="--wire-card-max-height: {maxHeight}"
            >
              {#if empty}
                <div class="wire-next__card-empty">
        <span class="{emptyIcon}" aria-hidden="true">
        </span>
        <strong>{emptyTitle}</strong>
        </div>
              {:else}
                {#if title}<h3>{title}</h3>{/if}
                {#if subtitle}<span class="wire-next__card-subtitle">{subtitle}</span>{/if}
                {#if description}<p>{description}</p>{/if}
                <slot />
                {#if actionLabel}
                  <a
                    class="wire-next__card-action"
                    href="{actionHref || '#'}"
                    @click="$emit('action', { label: actionLabel, href: actionHref })"
                  >
                    {actionLabel}<span class="icon-[lucide--chevron-right]" aria-hidden="true">
        </span>
        </a>
                {/if}
              {/if}
            </div>

            {#if footer}<footer class="wire-next__card-footer">{footer}</footer>{/if}

            {#if imageSrc && imagePosition === "bottom"}
              <div class="wire-next__card-media">
        <img
                  src="{imageSrc}"
                  alt="{imageAlt}"
                  loading="lazy"
                  @load="$emit('load', { src: imageSrc })"
                  @error="$emit('error', { src: imageSrc })"
                />
        </div>
            {/if}
          {/if}
        </article>
      {/if}
    </section>
    }
}
} }