diff --git a/src/design-system/primitives/Card/Card.module.css b/src/design-system/primitives/Card/Card.module.css
index aa9085b..6e88525 100644
--- a/src/design-system/primitives/Card/Card.module.css
+++ b/src/design-system/primitives/Card/Card.module.css
@@ -11,3 +11,22 @@
.accent-warning { border-top: 3px solid var(--warning); }
.accent-error { border-top: 3px solid var(--error); }
.accent-running { border-top: 3px solid var(--running); }
+
+.titleHeader {
+ padding: 12px 16px;
+ border-bottom: 1px solid var(--border-subtle);
+}
+
+.titleText {
+ font-size: 11px;
+ text-transform: uppercase;
+ font-family: var(--font-mono);
+ font-weight: 600;
+ color: var(--text-secondary);
+ letter-spacing: 0.5px;
+ margin: 0;
+}
+
+.body {
+ padding: 16px;
+}
diff --git a/src/design-system/primitives/Card/Card.test.tsx b/src/design-system/primitives/Card/Card.test.tsx
new file mode 100644
index 0000000..bff36e4
--- /dev/null
+++ b/src/design-system/primitives/Card/Card.test.tsx
@@ -0,0 +1,49 @@
+import { describe, it, expect } from 'vitest'
+import { render, screen } from '@testing-library/react'
+import { Card } from './Card'
+
+describe('Card', () => {
+ it('renders children', () => {
+ render(Hello world)
+ expect(screen.getByText('Hello world')).toBeInTheDocument()
+ })
+
+ it('renders title when provided', () => {
+ render(Content)
+ expect(screen.getByText('Status')).toBeInTheDocument()
+ })
+
+ it('does not render title header when title is omitted', () => {
+ const { container } = render(Content)
+ expect(container.querySelector('h3')).toBeNull()
+ })
+
+ it('wraps children in body div when title is provided', () => {
+ render(Content)
+ const content = screen.getByText('Content')
+ expect(content.parentElement).toHaveClass('body')
+ })
+
+ it('renders with accent and title together', () => {
+ const { container } = render(
+ Content,
+ )
+ const card = container.firstChild as HTMLElement
+ expect(card).toHaveClass('accent-success')
+ expect(screen.getByText('Health')).toBeInTheDocument()
+ expect(screen.getByText('Content')).toBeInTheDocument()
+ })
+
+ it('accepts className prop', () => {
+ const { container } = render(Content)
+ const card = container.firstChild as HTMLElement
+ expect(card).toHaveClass('custom')
+ })
+
+ it('renders children directly when no title (no wrapper div)', () => {
+ const { container } = render(Direct child)
+ const card = container.firstChild as HTMLElement
+ const span = screen.getByText('Direct child')
+ expect(span.parentElement).toBe(card)
+ })
+})
diff --git a/src/design-system/primitives/Card/Card.tsx b/src/design-system/primitives/Card/Card.tsx
index e6656d4..1595e0e 100644
--- a/src/design-system/primitives/Card/Card.tsx
+++ b/src/design-system/primitives/Card/Card.tsx
@@ -4,15 +4,25 @@ import type { ReactNode } from 'react'
interface CardProps {
children: ReactNode
accent?: 'amber' | 'success' | 'warning' | 'error' | 'running' | 'none'
+ title?: string
className?: string
}
-export function Card({ children, accent = 'none', className }: CardProps) {
+export function Card({ children, accent = 'none', title, className }: CardProps) {
const classes = [
styles.card,
accent !== 'none' ? styles[`accent-${accent}`] : '',
className ?? '',
].filter(Boolean).join(' ')
- return
{children}
+ return (
+
+ {title && (
+
+
{title}
+
+ )}
+ {title ?
{children}
: children}
+
+ )
}