Clickable Card
Dashboard tiles that navigate and a team list whose cards act: href renders a Card as an anchor, onclick as a button, and hover, focus and mint styles arrive only with a real click source. A side-by-side comparison covers why the href belongs on the Card, not on a wrapper around it.
Live preview
DashboardTiles.svelte
<a>: hover lifts it, and Tab reaches it as a single stop.<script lang="ts">
import { ArrowRightIcon, BuildingIcon, Card } from '@urbicon-ui/blocks';
</script>
<!-- The width cap fits the docs stage — size the grid in your dashboard
layout. The demo's links are fragments; your tiles take routes. -->
<div class="grid w-full max-w-2xl gap-4 sm:grid-cols-2">
<Card variant="elevated" padding="md" href="#revenue" mint="translate">
<div class="flex items-start justify-between">
<div>
<p class="text-text-tertiary text-xs font-medium">Revenue</p>
<p class="text-text-primary mt-1 text-2xl font-semibold tabular-nums">€42,150</p>
<p class="text-success mt-1 text-xs">+12.4% vs. last month</p>
</div>
<ArrowRightIcon size={16} class="text-text-tertiary" />
</div>
</Card>
<Card variant="elevated" padding="md" href="#buildings" mint="translate">
<div class="flex items-start justify-between">
<div>
<p class="text-text-tertiary text-xs font-medium">Properties</p>
<p class="text-text-primary mt-1 text-2xl font-semibold tabular-nums">12</p>
<p class="text-text-tertiary mt-1 text-xs">3 in progress</p>
</div>
<BuildingIcon size={16} class="text-text-tertiary" />
</div>
</Card>
</div>TeamList.svelte
onclick renders each card as a <button>.<script lang="ts">
import { ArrowRightIcon, Avatar, Card } from '@urbicon-ui/blocks';
// Stand-in for your action — open the user, start a selection; the demo
// only records who was clicked.
let lastClicked = $state<string | null>(null);
const users = [
{ id: '1', name: 'Anna Schulz', role: 'Administration' },
{ id: '2', name: 'Bernd Krüger', role: 'Accounting' },
{ id: '3', name: 'Cara Lentz', role: 'Caretaker' }
];
</script>
<div class="w-full max-w-lg">
<div class="space-y-2">
{#each users as user (user.id)}
<Card variant="quiet" padding="sm" onclick={() => (lastClicked = user.name)}>
<div class="flex items-center gap-3">
<Avatar name={user.name} size="sm" />
<div class="min-w-0 flex-1">
<p class="text-text-primary text-sm font-medium">{user.name}</p>
<p class="text-text-tertiary text-xs">{user.role}</p>
</div>
<ArrowRightIcon size={16} class="text-text-tertiary" />
</div>
</Card>
{/each}
</div>
{#if lastClicked}
<p class="text-text-tertiary mt-3 text-sm">
Last clicked: <span class="text-text-primary font-medium">{lastClicked}</span>
</p>
{/if}
</div>Where the click lives
Don't
<a href="/revenue">
<Card variant="elevated" padding="md">
Content
</Card>
</a> - The Card keeps rendering a
div: hover, focus and mint styles wait for a click source, so the card shows none of them. - A button or link inside the card now nests inside the outer
<a>(invalid HTML, an a11y violation).
Do
<Card
variant="elevated"
padding="md"
href="/revenue"
>
Content
</Card> - The card itself is the
<a>: one element, one tab stop, hover and focus styles included. - Cmd-click and middle-click open the route in a new tab, like on any link.
href for routes, onclick for actions
A destination takes href: the card renders as an <a>, and Cmd-click, middle-click and
open-in-new-tab work as on any link. An action (open a dialog, pick an entry) takes onclick: the card renders as a <button type="button">. The third case is clickable: it forces the button rendering without a
handler, for a wrapper component that owns the click handling itself.
No click source, no hover
Card has no decorative-hover mode: a pointer cursor and a lift on a card that goes nowhere
would promise an interaction that never comes (WCAG 3.2, Predictable). Hover, the focus
ring and mint switch on together with href, onclick or clickable. When a passive card seems to need the
interactive look, give it its click source; wrapping it in an <a> is the pattern the comparison above warns against.
One target per card
href and onclick turn the whole card into a single <a> or <button>, and HTML allows no interactive
content inside either. A row that needs a secondary action (a "Favorite" button, a menu)
keeps the Card passive and puts the targets inside it: the name becomes the link, the
action a Button of its own.