# Card Guides

## Patterns

### Settings Surface

When to use:

- A small set of related settings should share one surface.

Composition notes:

- Use headings and internal spacing explicitly.
- Keep the surface responsible for grouping, not for every spacing decision.

Trade-offs:

- Strong visual grouping.
- Easy to overuse as generic padding wrappers.

```tsx
import { FormEvent, useState } from 'react';
import { Button, Card, Heading, Label, Text, TextField, TextFieldInput } from '@vega-ui/react';

export default function SettingsFormCard() {
  const [saved, setSaved] = useState('');

  const submit = (event: FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    const data = new FormData(event.currentTarget);
    setSaved(`Saved “${data.get('workspace')}” at ${data.get('slug')}.vega.app`);
  };

  return (
    <Card asChild style={{ width: '100%', maxWidth: 420, gap: 'var(--spacing-12)' }}>
      <form aria-labelledby='workspace-card-title' onSubmit={submit} onReset={() => setSaved('')}>
        <div style={{ display: 'grid', gap: 'var(--spacing-2)' }}>
          <Heading id='workspace-card-title' as='h3' size={4} fontWeight={600}>Workspace</Heading>
          <Text size={2} style={{ color: 'var(--label-secondary)' }}>Shown in invitations and in the browser tab.</Text>
        </div>
        <div style={{ display: 'grid', gap: 'var(--spacing-8)', alignSelf: 'stretch' }}>
          <div style={{ display: 'grid', gap: 'var(--spacing-3)' }}>
            <Label htmlFor='workspace-card-name'>Name</Label>
            <TextField>
              <TextFieldInput id='workspace-card-name' name='workspace' defaultValue='Northwind Studio' required />
            </TextField>
          </div>
          <div style={{ display: 'grid', gap: 'var(--spacing-3)' }}>
            <Label htmlFor='workspace-card-slug'>URL</Label>
            <TextField>
              <TextFieldInput id='workspace-card-slug' name='slug' defaultValue='northwind' pattern='[a-z0-9-]{3,32}' required />
            </TextField>
          </div>
        </div>
        <div style={{ display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: 'var(--spacing-4)', alignSelf: 'stretch' }}>
          <Button type='submit' size='sm'>Save</Button>
          <Button type='reset' size='sm' variant='secondary' appearance='ghost'>Reset</Button>
          <Text size={2} role='status' style={{ color: 'var(--label-secondary)', overflowWrap: 'anywhere' }}>{saved}</Text>
        </div>
      </form>
    </Card>
  );
}
```

### Metrics Or Summary Surface

When to use:

- A dashboard or billing view needs grouped summary content.

Composition notes:

- Keep key metrics visually prominent within the card.
- Use content hierarchy, not only surface styling.

Trade-offs:

- Good for summaries and grouped metadata.
- Weak fit when the content is really just one inline label.

```tsx
import { useState } from 'react';
import { Button, Card, Heading, Spinner, Text } from '@vega-ui/react';

const formatter = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' });

export default function MetricsCard() {
  const [revenue, setRevenue] = useState(48210.4);
  const [updatedAt, setUpdatedAt] = useState('09:00');
  const [loading, setLoading] = useState(false);

  const refresh = () => {
    setLoading(true);
    setTimeout(() => {
      const now = new Date();
      setRevenue((current) => current + Math.round(Math.random() * 90000) / 100);
      setUpdatedAt(now.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', hour12: false }));
      setLoading(false);
    }, 900);
  };

  return (
    <Card aria-labelledby='revenue-title' aria-busy={loading} style={{ width: '100%', maxWidth: 300, gap: 'var(--spacing-2)' }}>
      <Heading id='revenue-title' as='h3' size={2} fontWeight={500} style={{ color: 'var(--label-secondary)' }}>Revenue this month</Heading>
      <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--spacing-4)', minHeight: 36 }}>
        <Text size={7} fontWeight={500} style={{ fontVariantNumeric: 'tabular-nums' }}>{formatter.format(revenue)}</Text>
        {loading && <Spinner size={3} aria-label='Refreshing revenue' />}
      </div>
      <Text size={2} style={{ color: 'var(--label-tertiary)', fontVariantNumeric: 'tabular-nums' }}>Updated at {updatedAt}</Text>
      <Button size='sm' variant='secondary' appearance='ghost' disabled={loading} onClick={refresh} style={{ marginBlockStart: 'var(--spacing-6)' }}>Refresh</Button>
    </Card>
  );
}
```

### Navigational Surface

When to use:

- One grouped surface should navigate to a deeper screen.

Composition notes:

- Use `asChild` with a real anchor or router link.
- Keep the destination understandable from the card content.

Trade-offs:

- Good for dashboard entry points.
- Easy to blur layout and navigation semantics if overused.

```tsx
import { Card, Heading, Text } from '@vega-ui/react';
import './examples.css';

const destinations = [
  { href: '#billing', title: 'Billing', description: 'Plan, invoices and payment methods.' },
  { href: '#members', title: 'Members', description: '18 members, 3 pending invitations.' },
  { href: '#security', title: 'Security', description: 'Two-factor authentication, sessions and SSO.' },
];

export default function LinkCards() {
  return (
    <nav aria-label='Workspace settings' style={{ width: '100%' }}>
      <ul style={{ listStyle: 'none', margin: 0, padding: 0, display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(min(200px, 100%), 1fr))', gap: 'var(--spacing-6)' }}>
        {destinations.map((destination) => (
          <li key={destination.href} style={{ display: 'grid' }}>
            <Card asChild size='sm' className='linkCard'>
              <a href={destination.href} style={{ color: 'inherit', textDecoration: 'none', gap: 'var(--spacing-2)' }}>
                <Heading as='h3' size={3} fontWeight={600}>{destination.title}</Heading>
                <Text size={2} style={{ color: 'var(--label-secondary)' }}>{destination.description}</Text>
              </a>
            </Card>
          </li>
        ))}
      </ul>
    </nav>
  );
}
```

## Comparison

### Quick Decision Rule

Use `Card` when grouped content needs a surface container.

### `Card` vs `Alert`

Use `Card` when:

- the content is grouped layout, not a feedback message

Use `Alert` when:

- the content is a short semantic feedback or status message

Main trade-off: card is a content surface, while alert is message-oriented feedback.

### `Card` vs `Badge`

Use `Card` when:

- multiple pieces of related content need grouping

Use `Badge` when:

- only a tiny status or category label is needed

Main trade-off: card is a container, while badge is a compact label.

### `Card` vs `Fieldset`

Use `Card` when:

- the grouping is layout or presentation

Use `Fieldset` when:

- the grouping is semantic form structure

### Choose This Component When

- Grouped content needs a distinct surface.
- The layout benefits from one bounded container.
- Headings, metrics, or settings should feel related.
- the component should stay neutral rather than encode feedback or status semantics

### Do Not Choose This Component When

- The content is just a short feedback message.
- The UI only needs a tiny status label.
- Surface styling would be the only source of hierarchy.

## Troubleshooting

### Card Content Feels Unstructured

#### Symptom

The surface exists, but users still cannot scan the content clearly.

#### Likely Cause

The card is being used as a generic wrapper without internal heading or spacing structure.

#### How To Verify

- Hide the card border mentally and inspect the remaining content hierarchy.

#### Fix

- Add headings, spacing, and clearer inner layout.
- Treat the card as grouping, not as the whole information architecture.

### Transparent Card Disappears Into The Background

#### Symptom

The grouped surface loses separation and feels visually flat.

#### Likely Cause

`appearance='transparent'` is used on a low-contrast or noisy background.

#### How To Verify

- Compare the transparent card against the real parent surface.

#### Fix

- Switch back to `outline`.
- Strengthen the parent surface contrast or content hierarchy.
