Skip to content
edmi-ui

Agent Card

AI agent card with a deterministic 5x5 identicon, badges and a stat row.

StackBlitz ↗StackBlitz ↗StackBlitz ↗
XSDAIAutopilot
dG6r…4mSr
Indexes
3
AUM
$12.4K
Best 7d
+4.1%
XSD
AI
Autopilot
dG6r…4mSr
Indexes
3
AUM
$12.4K
Best 7d
+4.1%
XSD AI Autopilot
dG6r…4mSr
Indexes
3
AUM
$12.4K
Best 7d
+4.1%
Terminal window
npx shadcn@latest add @edmi-ui/agent-card
Terminal window
pnpm dlx shadcn@latest add @edmi-ui/agent-card
Terminal window
yarn dlx shadcn@latest add @edmi-ui/agent-card
Terminal window
bunx shadcn@latest add @edmi-ui/agent-card

Pick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.

Terminal window
npx shadcn-vue@latest add @edmi-ui/agent-card
Terminal window
pnpm dlx shadcn-vue@latest add @edmi-ui/agent-card
Terminal window
yarn dlx shadcn-vue@latest add @edmi-ui/agent-card
Terminal window
bunx shadcn-vue@latest add @edmi-ui/agent-card

Pick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.

Terminal window
npx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/agent-card.json
Terminal window
pnpm dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/agent-card.json
Terminal window
yarn dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/agent-card.json
Terminal window
bunx --bun shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/agent-card.json

Pick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.

import { AgentCard } from "@/components/agent-card/agent-card";
<AgentCard
className="w-[380px] max-w-full"
name="XSD"
tag="AI"
status="Autopilot"
address="dG6r…4mSr"
stats={[
{ label: "Indexes", value: "3" },
{ label: "AUM", value: "$12.4K" },
{ label: "Best 7d", value: "+4.1%" },
]}
/>
<script setup lang="ts">
import { AgentCard } from "@/components/ui/agent-card";
</script>
<template>
<AgentCard
class="w-[380px] max-w-full"
name="XSD"
tag="AI"
status="Autopilot"
address="dG6r…4mSr"
:stats="[
{ label: 'Indexes', value: '3' },
{ label: 'AUM', value: '$12.4K' },
{ label: 'Best 7d', value: '+4.1%' },
]"
/>
</template>
<script lang="ts">
import { AgentCard } from "$lib/components/ui/agent-card";
</script>
<AgentCard
class="w-[380px] max-w-full"
name="XSD"
tag="AI"
status="Autopilot"
address="dG6r…4mSr"
stats={[
{ label: "Indexes", value: "3" },
{ label: "AUM", value: "$12.4K" },
{ label: "Best 7d", value: "+4.1%" },
]}
/>
registry/blocks/agent-card/agent-card.tsx
import { cn } from "cn";
import type * as React from "react";
import { Badge } from "@/registry/edmi/ui/badge";
import { Card } from "@/registry/edmi/ui/card";
import type { Elevation } from "@/registry/edmi/ui/elevation";
// Four tones of the board identicon: columns repeat muted-2 / muted / foreground-2 / empty.
const TONES = [
"bg-muted-foreground-2",
"bg-muted-foreground",
"bg-foreground-2",
"bg-transparent",
];
function hash(seed: string) {
let h = 2166136261;
for (let i = 0; i < seed.length; i++) {
h ^= seed.charCodeAt(i);
h = Math.imul(h, 16777619);
}
return h >>> 0;
}
type AgentIdenticonProps = React.ComponentProps<"div"> & {
/** Deterministic seed (agent name or address). */
seed: string;
/** Edge length in px. */
size?: number;
};
/** 5x5 token-colored identicon, deterministic per `seed`. */
function AgentIdenticon({
seed,
size = 44,
className,
style,
...props
}: AgentIdenticonProps) {
let h = hash(seed);
const cells = Array.from({ length: 25 }, (_, i) => {
if (i % 5 === 0) h = hash(`${seed}:${i}:${h}`);
return TONES[(h >>> ((i % 5) * 2)) & 3];
});
return (
<div
data-slot="agent-identicon"
aria-hidden="true"
className={cn(
"grid shrink-0 grid-cols-5 overflow-hidden rounded-xl border border-border bg-muted",
className,
)}
style={{ width: size, height: size, ...style }}
{...props}
>
{cells.map((tone, i) => (
// biome-ignore lint/suspicious/noArrayIndexKey: static grid
<span key={i} className={tone} />
))}
</div>
);
}
type AgentCardStat = { label: string; value: React.ReactNode };
type AgentCardProps = React.ComponentProps<typeof Card> & {
name: string;
/** Mono sub line, usually a shortened address. */
address?: string;
/** Secondary badge next to the name (e.g. "AI"). */
tag?: string;
/** Brand badge with a status dot (e.g. "Live"); omit to hide it. */
status?: React.ReactNode;
stats?: AgentCardStat[];
/** Identicon seed; defaults to `name`. */
seed?: string;
/** ✦ depth of the card (forwarded to Card). */
elevation?: Elevation;
// ✦ `children` render in a footer under the stats (a note, an action row).
};
function AgentCard({
className,
name,
address,
tag,
status,
stats,
seed,
elevation,
children,
...props
}: AgentCardProps) {
return (
<Card
data-slot="agent-card"
elevation={elevation}
className={cn("gap-0 p-5", className)}
{...props}
>
<div className="flex items-center gap-3.5">
<AgentIdenticon seed={seed ?? name} />
<div className="min-w-0">
<div className="flex flex-wrap items-center gap-2">
<span className="text-base font-semibold">{name}</span>
{tag ? <Badge variant="secondary">{tag}</Badge> : null}
{status ? (
<Badge variant="brand">
<span className="size-1.5 rounded-full bg-current" />
{status}
</Badge>
) : null}
</div>
{address ? (
<div className="mt-1 font-mono text-xs text-muted-foreground">
{address}
</div>
) : null}
</div>
</div>
{stats?.length ? (
<div className="mt-4 flex justify-between border-t border-border pt-3.5">
{stats.map((s) => (
<div key={s.label}>
<div className="text-xs text-muted-foreground">{s.label}</div>
<div className="mt-1 font-mono text-[19px]">{s.value}</div>
</div>
))}
</div>
) : null}
{children ? (
<div
data-slot="agent-card-footer"
className="mt-4 border-t border-border pt-3.5 text-[13px] text-muted-foreground"
>
{children}
</div>
) : null}
</Card>
);
}
export type { AgentCardStat };
export { AgentCard, AgentIdenticon };
registry/ui/agent-card/AgentCard.vue
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { Badge } from '@/registry/edmi/ui/badge'
import { Card } from '@/registry/edmi/ui/card'
import type { Elevation } from '@/registry/edmi/ui/elevation'
import { cn } from '@/registry/edmi/lib/utils'
import AgentIdenticon from './AgentIdenticon.vue'
import type { AgentCardStat } from './types'
const props = defineProps<{
/** ✦ depth of the card: sunken -1, flat 0, raised +1, floating +2 */
elevation?: Elevation
class?: HTMLAttributes['class']
name: string
/** Mono sub line, usually a shortened address. */
address?: string
/** Secondary badge next to the name (e.g. "AI"). */
tag?: string
/** Brand badge with a status dot (e.g. "Live"; or use the `status` slot); omit to hide it. */
status?: string
stats?: AgentCardStat[]
/** Identicon seed; defaults to `name`. */
seed?: string
}>()
</script>
<template>
<Card :elevation="elevation" data-slot="agent-card" :class="cn('gap-0 p-5', props.class)">
<div class="flex items-center gap-3.5">
<AgentIdenticon :seed="seed ?? name" />
<div class="min-w-0">
<div class="flex flex-wrap items-center gap-2">
<span class="text-base font-semibold">{{ name }}</span>
<Badge v-if="tag" variant="secondary">{{ tag }}</Badge>
<Badge v-if="status || $slots.status" variant="brand">
<span class="size-1.5 rounded-full bg-current" />
<slot name="status">{{ status }}</slot>
</Badge>
</div>
<div v-if="address" class="mt-1 font-mono text-xs text-muted-foreground">
{{ address }}
</div>
</div>
</div>
<div v-if="stats?.length" class="mt-4 flex justify-between border-t border-border pt-3.5">
<div v-for="s in stats" :key="s.label">
<div class="text-xs text-muted-foreground">{{ s.label }}</div>
<div class="mt-1 font-mono text-[19px]">
<slot name="stat-value" :stat="s">{{ s.value }}</slot>
</div>
</div>
</div>
<!-- ✦ default slot: footer under the stats (a note, an action row) -->
<div v-if="$slots.default" data-slot="agent-card-footer" class="mt-4 border-t border-border pt-3.5 text-[13px] text-muted-foreground">
<slot />
</div>
</Card>
</template>
registry/ui/agent-card/AgentIdenticon.vue
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { computed } from 'vue'
import { cn } from '@/registry/edmi/lib/utils'
// Four tones of the board identicon: columns repeat muted-2 / muted / foreground-2 / empty.
const TONES = ['bg-muted-foreground-2', 'bg-muted-foreground', 'bg-foreground-2', 'bg-transparent']
function hash(seed: string) {
let h = 2166136261
for (let i = 0; i < seed.length; i++) {
h ^= seed.charCodeAt(i)
h = Math.imul(h, 16777619)
}
return h >>> 0
}
const props = withDefaults(defineProps<{
class?: HTMLAttributes['class']
/** Deterministic seed (agent name or address). */
seed: string
/** Edge length in px. */
size?: number
}>(), { size: 44 })
const cells = computed(() => {
let h = hash(props.seed)
return Array.from({ length: 25 }, (_, i) => {
if (i % 5 === 0) h = hash(`${props.seed}:${i}:${h}`)
return TONES[(h >>> ((i % 5) * 2)) & 3]
})
})
</script>
<template>
<!-- 5x5 token-colored identicon, deterministic per `seed`. -->
<div
data-slot="agent-identicon"
aria-hidden="true"
:class="cn('grid shrink-0 grid-cols-5 overflow-hidden rounded-xl border border-border bg-muted', props.class)"
:style="{ width: `${size}px`, height: `${size}px` }"
>
<span v-for="(tone, i) in cells" :key="i" :class="tone" />
</div>
</template>
registry/ui/agent-card/index.ts
export { default as AgentCard } from "./AgentCard.vue";
export { default as AgentIdenticon } from "./AgentIdenticon.vue";
export type { AgentCardStat } from "./types";
registry/ui/agent-card/types.ts
export type AgentCardStat = { label: string; value: string | number };
src/lib/registry/ui/agent-card/agent-card.svelte
<script lang="ts" module>
import type { Snippet } from "svelte";
export type AgentCardStat = { label: string; value: string | number | Snippet };
</script>
<script lang="ts">
import { cn } from "$lib/utils.js";
import { Badge } from "$lib/registry/ui/badge/index.js";
import { Card } from "$lib/registry/ui/card/index.js";
import AgentIdenticon from "./agent-identicon.svelte";
import type { Elevation } from "$lib/registry/ui/elevation/index.js";
import type { ComponentProps } from "svelte";
let {
ref = $bindable(null),
class: className,
name,
address,
tag,
status,
stats,
seed,
elevation = "auto",
children,
...restProps
}: Omit<ComponentProps<typeof Card>, "children"> & {
/** ✦ Footer under the stats (a note, an action row). */
children?: Snippet;
name: string;
/** Mono sub line, usually a shortened address. */
address?: string;
/** Secondary badge next to the name (e.g. "AI"). */
tag?: string;
/** Brand badge with a status dot (e.g. "Live"); omit to hide it. */
status?: string | Snippet;
stats?: AgentCardStat[];
/** Identicon seed; defaults to `name`. */
seed?: string;
/** ✦ depth of the card (forwarded to Card). */
elevation?: Elevation;
} = $props();
</script>
<Card bind:ref data-slot="agent-card" {elevation} class={cn("gap-0 p-5", className)} {...restProps}>
<div class="flex items-center gap-3.5">
<AgentIdenticon seed={seed ?? name} />
<div class="min-w-0">
<div class="flex flex-wrap items-center gap-2">
<span class="text-base font-semibold">{name}</span>
{#if tag}<Badge variant="secondary">{tag}</Badge>{/if}
{#if status}
<Badge variant="brand">
<span class="size-1.5 rounded-full bg-current"></span>
{#if typeof status === "function"}{@render status()}{:else}{status}{/if}
</Badge>
{/if}
</div>
{#if address}
<div class="mt-1 font-mono text-xs text-muted-foreground">{address}</div>
{/if}
</div>
</div>
{#if stats?.length}
<div class="mt-4 flex justify-between border-t border-border pt-3.5">
{#each stats as s (s.label)}
<div>
<div class="text-xs text-muted-foreground">{s.label}</div>
<div class="mt-1 font-mono text-[19px]">
{#if typeof s.value === "function"}{@render s.value()}{:else}{s.value}{/if}
</div>
</div>
{/each}
</div>
{/if}
{#if children}
<div data-slot="agent-card-footer" class="mt-4 border-t border-border pt-3.5 text-[13px] text-muted-foreground">
{@render children()}
</div>
{/if}
</Card>
src/lib/registry/ui/agent-card/agent-identicon.svelte
<script lang="ts">
import { cn } from "$lib/utils.js";
import type { HTMLAttributes } from "svelte/elements";
// Four tones of the board identicon: columns repeat muted-2 / muted / foreground-2 / empty.
const TONES = ["bg-muted-foreground-2", "bg-muted-foreground", "bg-foreground-2", "bg-transparent"];
function hash(seed: string) {
let h = 2166136261;
for (let i = 0; i < seed.length; i++) {
h ^= seed.charCodeAt(i);
h = Math.imul(h, 16777619);
}
return h >>> 0;
}
let {
class: className,
seed,
size = 44,
style,
...restProps
}: HTMLAttributes<HTMLDivElement> & {
/** Deterministic seed (agent name or address). */
seed: string;
/** Edge length in px. */
size?: number;
} = $props();
const cells = $derived.by(() => {
let h = hash(seed);
return Array.from({ length: 25 }, (_, i) => {
if (i % 5 === 0) h = hash(`${seed}:${i}:${h}`);
return TONES[(h >>> ((i % 5) * 2)) & 3];
});
});
</script>
<!-- 5x5 token-colored identicon, deterministic per `seed`. -->
<div
data-slot="agent-identicon"
aria-hidden="true"
class={cn("grid shrink-0 grid-cols-5 overflow-hidden rounded-xl border border-border bg-muted", className)}
style="width: {size}px; height: {size}px;{style ? ` ${style}` : ''}"
{...restProps}
>
{#each cells as tone, i (i)}
<span class={tone}></span>
{/each}
</div>
src/lib/registry/ui/agent-card/index.ts
import Root, { type AgentCardStat } from "./agent-card.svelte";
import Identicon from "./agent-identicon.svelte";
export {
type AgentCardStat,
Identicon,
Identicon as AgentIdenticon,
Root,
//
Root as AgentCard,
};

See the Elevation guide for the levels, roles and layered mode.

Flat by default. elevation is forwarded to the card: sunken (-1) a soft inset well, raised (+1) the bevel, floating (+2) the bevel plus one soft drop. In a layered ElevationProvider the card resolves to its role default (raised).

StackBlitz ↗StackBlitz ↗StackBlitz ↗

Flat (0)

XSDAIAutopilot
dG6r…4mSr
Indexes
3
AUM
$12.4K
Best 7d
+4.1%

Raised (+1)

XSDAIAutopilot
dG6r…4mSr
Indexes
3
AUM
$12.4K
Best 7d
+4.1%

Floating (+2)

XSDAIAutopilot
dG6r…4mSr
Indexes
3
AUM
$12.4K
Best 7d
+4.1%

Flat (0)

XSD
AI
Autopilot
dG6r…4mSr
Indexes
3
AUM
$12.4K
Best 7d
+4.1%

Raised (+1)

XSD
AI
Autopilot
dG6r…4mSr
Indexes
3
AUM
$12.4K
Best 7d
+4.1%

Floating (+2)

XSD
AI
Autopilot
dG6r…4mSr
Indexes
3
AUM
$12.4K
Best 7d
+4.1%

Flat (0)

XSD AI Autopilot
dG6r…4mSr
Indexes
3
AUM
$12.4K
Best 7d
+4.1%

Raised (+1)

XSD AI Autopilot
dG6r…4mSr
Indexes
3
AUM
$12.4K
Best 7d
+4.1%

Floating (+2)

XSD AI Autopilot
dG6r…4mSr
Indexes
3
AUM
$12.4K
Best 7d
+4.1%
Part Prop Type Default
AgentCard name string required
address string (mono sub line) none
tag string (secondary badge) none
status brand badge with a dot, e.g. Live (React ReactNode, Vue status slot, Svelte string | Snippet) none
stats { label, value }[] (value: React ReactNode, Vue stat-value scoped slot, Svelte string | number | Snippet) none
seed string identicon seed name
elevation "auto" | "sunken" | "flat" | "raised" | "floating" "auto"
children ✦ React children, Vue default slot, Svelte children snippet: footer under the stats none
AgentIdenticon seed, size token-coloured 5x5 grid, deterministic per seed size 44

AgentIdenticon is intentionally not merged with the AI pack’s Agent Avatar: both are seeded 5x5 identicons, but the identicon here is a full grid painted in neutral token tones (--muted-foreground-2, --muted-foreground, --foreground-2) on a muted tile for list cards, while Agent Avatar is a mirrored 3x5 mark tinted with a chart color. Each keeps its own look.

Children render in a footer under the stats: a note, an action row.

StackBlitz ↗StackBlitz ↗StackBlitz ↗
XSDAIAutopilot
dG6r…4mSr
Indexes
3
AUM
$12.4K
Best 7d
+4.1%
Rebalanced MAG4 weights 2h ago.
XSD
AI
Autopilot
dG6r…4mSr
Indexes
3
AUM
$12.4K
Best 7d
+4.1%
Rebalanced MAG4 weights 2h ago.
XSD AI Autopilot
dG6r…4mSr
Indexes
3
AUM
$12.4K
Best 7d
+4.1%
Rebalanced MAG4 weights 2h ago.
  • Whole block is an Edmi addition (DESIGN §5 Patterns, board 12). Composed from: card, badge.
  • Install: shadcn add @edmi-ui/agent-card.