diff --git a/ui/src/components/AlertStateChip.test.tsx b/ui/src/components/AlertStateChip.test.tsx
new file mode 100644
index 00000000..dbdc0785
--- /dev/null
+++ b/ui/src/components/AlertStateChip.test.tsx
@@ -0,0 +1,25 @@
+import { describe, it, expect } from 'vitest';
+import { render, screen } from '@testing-library/react';
+import { ThemeProvider } from '@cameleer/design-system';
+import { AlertStateChip } from './AlertStateChip';
+
+function renderWithTheme(ui: React.ReactElement) {
+ return render({ui});
+}
+
+describe('AlertStateChip', () => {
+ it.each([
+ ['PENDING', /pending/i],
+ ['FIRING', /firing/i],
+ ['ACKNOWLEDGED', /acknowledged/i],
+ ['RESOLVED', /resolved/i],
+ ] as const)('renders %s label', (state, pattern) => {
+ renderWithTheme();
+ expect(screen.getByText(pattern)).toBeInTheDocument();
+ });
+
+ it('shows silenced suffix when silenced=true', () => {
+ renderWithTheme();
+ expect(screen.getByText(/silenced/i)).toBeInTheDocument();
+ });
+});
diff --git a/ui/src/components/AlertStateChip.tsx b/ui/src/components/AlertStateChip.tsx
new file mode 100644
index 00000000..a81548f0
--- /dev/null
+++ b/ui/src/components/AlertStateChip.tsx
@@ -0,0 +1,27 @@
+import { Badge } from '@cameleer/design-system';
+import type { AlertDto } from '../api/queries/alerts';
+
+type State = NonNullable;
+
+const LABELS: Record = {
+ PENDING: 'Pending',
+ FIRING: 'Firing',
+ ACKNOWLEDGED: 'Acknowledged',
+ RESOLVED: 'Resolved',
+};
+
+const COLORS: Record = {
+ PENDING: 'warning',
+ FIRING: 'error',
+ ACKNOWLEDGED: 'warning',
+ RESOLVED: 'success',
+};
+
+export function AlertStateChip({ state, silenced }: { state: State; silenced?: boolean }) {
+ return (
+
+
+ {silenced && }
+
+ );
+}
diff --git a/ui/src/components/SeverityBadge.test.tsx b/ui/src/components/SeverityBadge.test.tsx
new file mode 100644
index 00000000..a685fe12
--- /dev/null
+++ b/ui/src/components/SeverityBadge.test.tsx
@@ -0,0 +1,19 @@
+import { describe, it, expect } from 'vitest';
+import { render, screen } from '@testing-library/react';
+import { ThemeProvider } from '@cameleer/design-system';
+import { SeverityBadge } from './SeverityBadge';
+
+function renderWithTheme(ui: React.ReactElement) {
+ return render({ui});
+}
+
+describe('SeverityBadge', () => {
+ it.each([
+ ['CRITICAL', /critical/i],
+ ['WARNING', /warning/i],
+ ['INFO', /info/i],
+ ] as const)('renders %s', (severity, pattern) => {
+ renderWithTheme();
+ expect(screen.getByText(pattern)).toBeInTheDocument();
+ });
+});
diff --git a/ui/src/components/SeverityBadge.tsx b/ui/src/components/SeverityBadge.tsx
new file mode 100644
index 00000000..b4d712ac
--- /dev/null
+++ b/ui/src/components/SeverityBadge.tsx
@@ -0,0 +1,20 @@
+import { Badge } from '@cameleer/design-system';
+import type { AlertDto } from '../api/queries/alerts';
+
+type Severity = NonNullable;
+
+const LABELS: Record = {
+ CRITICAL: 'Critical',
+ WARNING: 'Warning',
+ INFO: 'Info',
+};
+
+const COLORS: Record = {
+ CRITICAL: 'error',
+ WARNING: 'warning',
+ INFO: 'auto',
+};
+
+export function SeverityBadge({ severity }: { severity: Severity }) {
+ return ;
+}