Phi
Layer Card
@dicehub/phiv1.0.0-beta.1

Layer Card

A card component with a layered visual effect, perfect for navigation or feature highlights.

<script setup>
import { Button } from "@dicehub/phi/components/button";
import { LayerCard } from "@dicehub/phi/components/layer-card";
import { PhArrowRight } from "@phosphor-icons/vue";
</script>

<template>
  <LayerCard>
    <LayerCard.Secondary class="flex items-center justify-between">
      <span>Next Steps</span>
      <Button
        :icon="PhArrowRight"
        :icon-props="{ size: 16 }"
        aria-label="Go to next steps"
        shape="square"
        size="sm"
        variant="ghost"
      />
    </LayerCard.Secondary>
    <LayerCard.Primary>Get started with Phi</LayerCard.Primary>
  </LayerCard>
</template>

Installation

Barrel

import { LayerCard } from "@dicehub/phi";

Granular

import { LayerCard } from "@dicehub/phi/components/layer-card";

Usage

<script setup>
import { LayerCard } from "@dicehub/phi/components/layer-card";
</script>

<template>
  <LayerCard style="width: 250px;">
    <LayerCard.Secondary>Documentation</LayerCard.Secondary>
    <LayerCard.Primary>Learn how to use Phi components</LayerCard.Primary>
  </LayerCard>
</template>

LayerCard also supports a simple surface-style mode for cases where you do not need a secondary header row. Render content directly inside LayerCard instead of wrapping it in LayerCard.Primary.

<script setup>
import { LayerCard } from "@dicehub/phi/components/layer-card";
</script>

<template>
  <LayerCard style="width: 250px; padding: 1rem;">
    Learn how to use Phi components
  </LayerCard>
</template>

Examples

Basic Card

<script setup>
import { LayerCard } from "@dicehub/phi/components/layer-card";
</script>

<template>
  <LayerCard style="width: 250px;">
    <LayerCard.Secondary>Getting Started</LayerCard.Secondary>
    <LayerCard.Primary>
      <p class="text-sm text-subtle">Quick start guide for new users</p>
    </LayerCard.Primary>
  </LayerCard>
</template>

Surface-style Card

For simple card containers, render content directly inside LayerCard without LayerCard.Primary.

<script setup>
import { LayerCard } from "@dicehub/phi/components/layer-card";
</script>

<template>
  <LayerCard style="width: 250px; padding: 1rem;">
    <p class="text-sm text-subtle">Quick start guide for new users</p>
  </LayerCard>
</template>

Multiple Cards

<script setup>
import { LayerCard } from "@dicehub/phi/components/layer-card";
</script>

<template>
  <div class="flex gap-4">
    <LayerCard style="width: 200px;">
      <LayerCard.Secondary>Components</LayerCard.Secondary>
      <LayerCard.Primary>
        <p class="text-sm">Browse all components</p>
      </LayerCard.Primary>
    </LayerCard>

    <LayerCard style="width: 200px;">
      <LayerCard.Secondary>Examples</LayerCard.Secondary>
      <LayerCard.Primary>
        <p class="text-sm">View code examples</p>
      </LayerCard.Primary>
    </LayerCard>
  </div>
</template>

Filter Toolbar with Small Tabs

Combine a small input with compact filter controls inside LayerCard for dense filter toolbars.

<script setup>
import { computed, ref } from "vue";
import { Badge } from "@dicehub/phi/components/badge";
import { Input } from "@dicehub/phi/components/input";
import { LayerCard } from "@dicehub/phi/components/layer-card";

const origins = [
  { origin: "challenges.cloudflare.com", s2xx: 1, s4xx: 0, duration: "95.4ms" },
  { origin: "Unknown", s2xx: 19, s4xx: 7, duration: "463.7ms" },
  { origin: "api.example.com", s2xx: 42, s4xx: 3, duration: "128.1ms" },
];

const filter = ref("all");
const search = ref("");
const filteredOrigins = computed(() =>
  origins.filter((origin) => {
    if (filter.value === "2xx" && origin.s2xx === 0) return false;
    if (filter.value === "4xx" && origin.s4xx === 0) return false;
    if (["3xx", "5xx"].includes(filter.value)) return false;
    return origin.origin.toLowerCase().includes(search.value.toLowerCase());
  }),
);
</script>

<template>
  <LayerCard style="width: min(100%, 540px);">
    <LayerCard.Secondary>Subrequests</LayerCard.Secondary>
    <LayerCard.Primary>
      <div class="toolbar">
        <Input v-model="search" size="sm" placeholder="Filter origins..." />
        <button @click="filter = 'all'">All</button>
        <button @click="filter = '2xx'">2xx</button>
        <button @click="filter = '4xx'">4xx</button>
      </div>

      <div v-for="origin in filteredOrigins" :key="origin.origin">
        <span>{{ origin.origin }}</span>
        <Badge v-if="origin.s2xx > 0" variant="success">2xx {{ origin.s2xx }}</Badge>
        <Badge v-if="origin.s4xx > 0" variant="error">4xx {{ origin.s4xx }}</Badge>
        <span>{{ origin.duration }}</span>
      </div>
    </LayerCard.Primary>
  </LayerCard>
</template>

Test IDs

LayerCard.Primary and LayerCard.Secondary accept standard HTML attributes, including data-testid for testing.

<script setup>
import { LayerCard } from "@dicehub/phi/components/layer-card";
</script>

<template>
  <LayerCard style="width: 250px;">
    <LayerCard.Secondary data-testid="card-header">
      Getting Started
    </LayerCard.Secondary>
    <LayerCard.Primary data-testid="card-body">
      <p class="text-sm text-subtle">Quick start guide for new users</p>
    </LayerCard.Primary>
  </LayerCard>
</template>

API Reference

PropTypeDefaultDescription
asstring"div"HTML element used for the rendered component.
default slotslot-Card content. Use LayerCard.Secondary and LayerCard.Primary for the layered treatment.
classstring--
idstring--
langstring--
titlestring--