# Audit Log

> Dense security audit log in a card: one timeline row per event with the actor in bold, a right-aligned time and a muted Geist Mono line of IP, key or device detail, plus an Export button.

A shadcn/ui block from the [@01orbital](https://01orbital.dev/?utm_source=shadcn.exchange) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/01orbital/audit-log).

- Page, with a live preview: https://shadcn.exchange/r/01orbital/audit-log
- Screenshot: https://shadcn.exchange/shot/01orbital/audit-log/light.png
- Registry item: https://01orbital.dev/r/audit-log.json
- Categories: feed
- Listed: 2026-11-01, source last changed 2026-11-01

## Install

```bash
npx shadcn@latest add https://shadcn.exchange/r/01orbital/audit-log.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- Registry items: `@01orbital/button`, `card`, `@01orbital/font-geist-mono`, `@01orbital/timeline`

## Files

### `blocks/feed/audit-log/index.tsx`

```tsx
import {
  Timeline,
  TimelineContent,
  TimelineIndicator,
  TimelineItem,
  TimelineLabel,
  TimelineTitle,
} from "@/components/editorial/timeline"
import { Button } from "@/components/ui/button"
import {
  Card,
  CardAction,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from "@/components/ui/card"

// The copy: change text here, not in the markup.
const COPY = {
  title: "Audit log",
  body: "Security-relevant events on this workspace.",
  cta: "Export",
} as const

const EVENTS = [
  {
    actor: "Lena Orvik",
    action: "changed the billing email",
    meta: "lena@orbcore.dev · 193.0.12.4",
    time: "14:02",
  },
  {
    actor: "Tomas Vey",
    action: "created an API key",
    meta: "ok_live_…9f2a · 10.4.0.18",
    time: "13:47",
  },
  {
    actor: "Ines Halloran",
    action: "removed Elias Quill from Admins",
    meta: "193.0.12.9",
    time: "11:20",
  },
  {
    actor: "System",
    action: "rotated the signing secret",
    meta: "scheduled",
    time: "03:00",
  },
  {
    actor: "Kofi Brandt",
    action: "signed in with a new device",
    meta: "macOS · Chrome · Lisbon",
    time: "Yesterday 22:15",
  },
  {
    actor: "Mira Sato",
    action: "enabled two-factor sign-in",
    meta: "193.0.12.2",
    time: "Yesterday 09:31",
  },
] as const

// Dense: one line per event, the detail in a muted mono line under it. The
// actor is the bold name, not a mark; the dot only places the row in time.
export default function AuditLog() {
  return (
    <Card className="mx-auto w-full max-w-md">
      <CardHeader>
        <CardTitle>{COPY.title}</CardTitle>
        <CardDescription>{COPY.body}</CardDescription>
        <CardAction>
          <Button variant="outline" size="sm">
            {COPY.cta}
          </Button>
        </CardAction>
      </CardHeader>
      <CardContent>
        <Timeline className="[--timeline-lh:1.25rem]">
          {EVENTS.map((e) => (
            <TimelineItem key={e.action + e.time}>
              <TimelineIndicator className="border-0 [&_[data-slot=timeline-dot]]:bg-muted-foreground/60" />
              <TimelineContent className="pb-5">
                <div className="flex items-baseline justify-between gap-3">
                  <TimelineTitle className="text-sm/5 font-normal">
                    <span className="font-medium">{e.actor}</span> {e.action}
                  </TimelineTitle>
                  <TimelineLabel className="shrink-0 tabular-nums">
                    {e.time}
                  </TimelineLabel>
                </div>
                <p className="mt-0.5 font-mono text-xs text-muted-foreground">
                  {e.meta}
                </p>
              </TimelineContent>
            </TimelineItem>
          ))}
        </Timeline>
      </CardContent>
    </Card>
  )
}
```
