<script setup>
import { Badge } from "@dicehub/kappa/components/badge";
import { LayerCard } from "@dicehub/kappa/components/layer-card";
</script>
<template>
<LayerCard style="width: 23rem;">
<LayerCard.Secondary as="header">
<span>Run 214</span>
<Badge variant="success">Converged</Badge>
</LayerCard.Secondary>
<LayerCard.Primary>
<strong>Steady-state thermal analysis</strong>
<span>Residual 2.1e-05 after 42 iterations.</span>
</LayerCard.Primary>
</LayerCard>
</template>Installation
Layer Card is part of the main Kappa package. It uses native semantic elements and adds no state-machine dependency.
Barrel
import {
LayerCard,
LayerCardPrimary,
LayerCardRoot,
LayerCardSecondary,
} from "@dicehub/kappa";Granular
import {
LayerCard,
LayerCardPrimary,
LayerCardRoot,
LayerCardSecondary,
} from "@dicehub/kappa/components/layer-card";Usage
Place Secondary first for context such as a run identifier, resource name, or category. Primary then carries the raised content and stays flush with the outer surface.
- Iterations
- 42
- Wall time
- 4 min 12 s
<script setup>
import { LayerCard } from "@dicehub/kappa/components/layer-card";
</script>
<template>
<LayerCard as="article" style="width: 23rem;">
<LayerCard.Secondary as="header">Solver configuration</LayerCard.Secondary>
<LayerCard.Primary>
<strong>Rotor refinement</strong>
<span>Mesh 4.2M cells · k-ω SST · 8 workers</span>
<dl>
<div><dt>Iterations</dt><dd>42</dd></div>
<div><dt>Wall time</dt><dd>4 min 12 s</dd></div>
</dl>
</LayerCard.Primary>
</LayerCard>
</template>Composition
LayerCard.Root <div | article | section | aside | a | form>
├── LayerCard.Secondary (optional)
│ └── supporting label or context
└── LayerCard.Primary
└── raised content
LayerCard.Root (simple surface, direct content)
└── default slotThe root owns the outer radius and clips both layers. The primary surface stays square, so the outer silhouette supplies every visible corner. Ark UI has no static layer primitive, so Kappa owns this native presentation and its semantic element choices.
- Use Secondary and Primary together for two clear surfaces.
- Use direct root content when one surface is enough.
- Place dense toolbars, filters, and tables inside Primary; keep Secondary to one line of context.
- Keep one primary target per layer. Do not nest other controls inside a linked surface.
- Do not nest Layer Cards to fake depth. Use Grid or another layout container.
Examples
Layered Content
Pair a secondary context layer with one raised primary surface.
- Services
- 4
- Checks
- 12 / 12
<script setup>
import { LayerCard } from "@dicehub/kappa/components/layer-card";
</script>
<template>
<LayerCard as="article" style="width: 23rem;">
<LayerCard.Secondary as="header">Deployment window</LayerCard.Secondary>
<LayerCard.Primary>
<strong>Tuesday, 18:00–19:00 UTC</strong>
<span>Four services, twelve checks, no maintenance window conflicts.</span>
<dl>
<div><dt>Services</dt><dd>4</dd></div>
<div><dt>Checks</dt><dd>12 / 12</dd></div>
</dl>
</LayerCard.Primary>
</LayerCard>
</template>Simple Surface
Use direct root content when one surface is enough.
Solver diagnostics
All convergence checks passed. The solution is ready to export.
<script setup>
import { LayerCard } from "@dicehub/kappa/components/layer-card";
</script>
<template>
<LayerCard as="section" style="width: 23rem;">
<p><strong>Solver diagnostics</strong></p>
<p>All convergence checks passed. The solution is ready to export.</p>
</LayerCard>
</template>Interactive Primary Link
Render Primary as an anchor when the whole layer has one target.
<script setup>
import { ArrowRight } from "@lucide/vue";
import { LayerCard } from "@dicehub/kappa/components/layer-card";
</script>
<template>
<LayerCard style="width: 23rem;">
<LayerCard.Secondary as="header">Release notes</LayerCard.Secondary>
<LayerCard.Primary as="a" href="/release-notes">
<strong>Read the component model <ArrowRight aria-hidden="true" /></strong>
<span>The primary layer is one semantic anchor.</span>
</LayerCard.Primary>
</LayerCard>
</template>Layered States
Hover and focus-visible styles cover linked roots and linked primaries.
<script setup>
import { LayerCard } from "@dicehub/kappa/components/layer-card";
</script>
<template>
<LayerCard as="a" href="/reports/run-214" style="width: 23rem;">
<span>Linked root surface</span>
<span>Hover and keyboard focus are visible.</span>
</LayerCard>
<LayerCard style="width: 23rem;">
<LayerCard.Secondary as="header">Primary link</LayerCard.Secondary>
<LayerCard.Primary as="a" href="/reports/run-214">
<strong>Open the run report</strong>
<span>Focus stays inside the layered surface.</span>
</LayerCard.Primary>
</LayerCard>
</template>Filter Toolbar with Small Tabs
Compose small tabs, a search input, and a status table inside one dense primary layer.
| Origin | Status | Duration |
|---|---|---|
| api.dicehub.com /v1/runs | 2xx · 1842 | 118 ms |
| cdn.dicehub.com /preview/* | 2xx · 3208 | 42 ms |
| assets.dicehub.com /meshes/* | 3xx · 412 | 64 ms |
| legacy.dicehub.com /v0/solve | 4xx · 96 | 812 ms |
| telemetry.dicehub.com /ingest | 4xx · 58 | 310 ms |
| gateway.dicehub.com /solver/health | 5xx · 12 | 2.4 s |
<script setup>
import { computed, ref } from "vue";
import { Badge } from "@dicehub/kappa/components/badge";
import { Input } from "@dicehub/kappa/components/input";
import { LayerCard } from "@dicehub/kappa/components/layer-card";
import { Tabs } from "@dicehub/kappa/components/tabs";
const statusFilters = [
{ value: "all", label: "All" },
{ value: "2xx", label: "2xx" },
{ value: "3xx", label: "3xx" },
{ value: "4xx", label: "4xx" },
{ value: "5xx", label: "5xx" },
];
const statusVariants = { "2xx": "success", "3xx": "info", "4xx": "warning", "5xx": "error" };
const requests = [
{ origin: "api.dicehub.com /v1/runs", status: "2xx", count: 1842, duration: "118 ms" },
{ origin: "legacy.dicehub.com /v0/solve", status: "4xx", count: 96, duration: "812 ms" },
{ origin: "gateway.dicehub.com /solver/health", status: "5xx", count: 12, duration: "2.4 s" },
// …more origins
];
const search = ref("");
const status = ref("all");
const visible = computed(() => {
const query = search.value.trim().toLowerCase();
return requests.filter((request) =>
(status.value === "all" || request.status === status.value) &&
(!query || request.origin.toLowerCase().includes(query)),
);
});
</script>
<template>
<LayerCard as="section" style="width: 34rem;">
<LayerCard.Secondary as="header">
<span>Request log</span>
<span>{{ visible.length }} / {{ requests.length }}</span>
</LayerCard.Secondary>
<LayerCard.Primary>
<div class="toolbar">
<Input v-model="search" aria-label="Filter origins" size="sm" placeholder="Filter origins..." />
<Tabs.Root v-model="status">
<Tabs.List aria-label="Filter by status" size="sm">
<Tabs.Trigger v-for="filter in statusFilters" :key="filter.value" :value="filter.value">
{{ filter.label }}
</Tabs.Trigger>
<Tabs.Indicator />
</Tabs.List>
</Tabs.Root>
</div>
<table aria-label="Request origins">
<thead>
<tr><th scope="col">Origin</th><th scope="col">Status</th><th scope="col">Duration</th></tr>
</thead>
<tbody>
<tr v-for="request in visible" :key="request.origin">
<th scope="row">{{ request.origin }}</th>
<td><Badge :variant="statusVariants[request.status]">{{ request.status }} · {{ request.count }}</Badge></td>
<td>{{ request.duration }}</td>
</tr>
</tbody>
</table>
<p v-if="visible.length === 0" role="status">No origins match the current filter.</p>
<p>Showing {{ visible.length }} of {{ requests.length }} requests</p>
</LayerCard.Primary>
</LayerCard>
</template>Accessibility
- Layer Card adds no widget role or keyboard behavior.
- Choose article, section, aside, and header only when the content meets native HTML sectioning rules.
- Use
as="a"on the root or on Primary only when the complete surface has one target. Do not place other controls inside that anchor. - Linked roots and linked primaries keep a visible focus indicator and a forced-colors boundary.
- HTML, ARIA, data attributes, and event listeners pass to the selected native element.
API Reference
LayerCard
Outer surface for layered or direct content.
| Prop | Type | Default | Description |
|---|---|---|---|
as | "div" | "article" | "section" | "aside" | "a" | "form" | "div" | Native element rendered by the outer surface. |
default slot | slot | — | Secondary and Primary layers, or direct surface content. |
Parts
| Part | Supported as | Default | Description |
|---|---|---|---|
LayerCard.Secondary | "div" | "header" | "p" | "div" | Supporting layer behind the primary surface. |
LayerCard.Primary | "div" | "article" | "section" | "a" | "div" | Raised content surface with square corners. |
Data Slots
| Slot | Description |
|---|---|
layer-card | Outer surface |
layer-card-secondary | Supporting layer |
layer-card-primary | Raised primary layer |
Exports
| Export | Description |
|---|---|
LayerCard | Compound root with the Root, Primary, and Secondary parts. |
LayerCardRoot | Named outer surface export. |
LayerCardPrimary / LayerCardSecondary | Named layered-surface exports. |
LayerCard*Props / LayerCard*Slots | Public prop and slot contracts. |
resolveLayerCard* | Safe runtime resolvers for the semantic element props. |