Audit log
A source-copy activity feed with consumer-owned events, filters, actions, timestamps, permissions, and persistence.
Present account activity without owning audit infrastructureLink to section
AuditLogPanel renders a named activity section, ordered events, machine-readable
timestamps, optional details, filters and actions. Your application supplies the
audit data and decides which controls are available.
Presentation only
This Block does not collect events, write an audit trail, fetch activity, authorize viewers, retain logs or perform mutations. Treat production audit ingestion, integrity, retention and access control as application/backend responsibilities.
Canonical identityLink to section
- Category: Account & workspace, slug
account. - Block:
audit-log, exportAuditLogPanel. - Canonical source:
apps/marketing/content/blocks/account/audit-log/. - Complete entry point:
index.tsx.
There is no Blocks npm package, Registry installer or CLI in this flow. The canonical file is React presentation source that you copy into your application.
Copy sourceLink to section
Configure the public UI package and tokens, then create
src/components/blocks/audit-log/index.tsx in your application. Open the source
below and use its copy button, or select the code manually. This is the complete
canonical file, not a shortened example or a second implementation.
View and copy the complete source
import * as React from "react";
export interface AuditLogEvent {
/** Stable identity, unique within this panel; used as the React key. */
id: string;
actor: string;
action: string;
target?: string;
/** Machine-readable timestamp, ideally ISO 8601. */
occurredAt: string;
/** Human-readable timestamp controlled by the consuming application. */
occurredAtLabel: string;
details?: React.ReactNode;
actions?: React.ReactNode;
}
export interface AuditLogPanelProps {
/** Stable HTML id, unique across the consuming page. */
id: string;
heading: string;
description?: string;
events: readonly AuditLogEvent[];
filters?: React.ReactNode;
actions?: React.ReactNode;
emptyTitle?: string;
emptyDescription?: string;
className?: string;
}
function hasContent(children?: React.ReactNode) {
return React.Children.toArray(children).some((child) => child !== "");
}
function Slot({
children,
slot,
}: {
children?: React.ReactNode;
slot: string;
}) {
if (!hasContent(children)) return null;
return (
<div
data-slot={slot}
className="flex min-w-0 flex-wrap items-center gap-2 [&>*]:max-w-full [&>*]:whitespace-normal [&>*]:break-words"
>
{children}
</div>
);
}
export function AuditLogPanel({
id,
heading,
description,
events,
filters,
actions,
emptyTitle = "No activity to display",
emptyDescription,
className,
}: AuditLogPanelProps) {
const headingId = `${id}-heading`;
const descriptionId = description ? `${id}-description` : undefined;
return (
<section
id={id}
aria-labelledby={headingId}
aria-describedby={descriptionId}
data-slot="audit-log-panel"
className={[
"min-w-0 rounded-xl border border-border bg-card text-card-foreground",
className,
]
.filter(Boolean)
.join(" ")}
>
<div className="flex min-w-0 flex-col gap-4 border-b border-border p-4 sm:p-6 lg:flex-row lg:items-start lg:justify-between">
<div className="min-w-0 space-y-1">
<h2 id={headingId} className="break-words text-base font-semibold">
{heading}
</h2>
{description ? (
<p
id={descriptionId}
className="break-words text-sm text-muted-foreground"
>
{description}
</p>
) : null}
</div>
<div className="flex min-w-0 flex-col gap-2 sm:flex-row sm:flex-wrap sm:items-center">
<Slot slot="audit-log-filters">{filters}</Slot>
<Slot slot="audit-log-actions">{actions}</Slot>
</div>
</div>
{events.length > 0 ? (
<ol
role="list"
aria-labelledby={headingId}
className="divide-y divide-border"
>
{events.map((event) => (
<li
key={event.id}
data-slot="audit-log-event"
className="min-w-0 p-4 sm:p-6"
>
<article className="flex min-w-0 flex-col gap-4 lg:flex-row lg:items-start lg:justify-between">
<div className="min-w-0 flex-1 space-y-2">
<p className="min-w-0 break-words text-sm leading-6">
<span className="font-medium">{event.actor}</span>{" "}
<span>{event.action}</span>
{event.target ? (
<>
{" "}
<span className="font-medium">{event.target}</span>
</>
) : null}
</p>
<time
dateTime={event.occurredAt}
className="block break-words text-xs text-muted-foreground"
>
{event.occurredAtLabel}
</time>
{hasContent(event.details) ? (
<div
data-slot="audit-log-details"
className="min-w-0 break-words text-sm text-muted-foreground [&_pre]:max-w-full [&_pre]:overflow-x-auto"
>
{event.details}
</div>
) : null}
</div>
<Slot slot="audit-log-event-actions">{event.actions}</Slot>
</article>
</li>
))}
</ol>
) : (
<div
data-slot="audit-log-empty"
className="min-w-0 space-y-1 p-6 text-center sm:p-8"
>
<p className="break-words text-sm font-medium">{emptyTitle}</p>
{emptyDescription ? (
<p className="break-words text-sm text-muted-foreground">
{emptyDescription}
</p>
) : null}
</div>
)}
</section>
);
}
The documentation includes this file from canonical source at build time. Your application copy does not receive automatic updates. Connect your own audit data and permissions, then validate it in your application. Return to the Blocks catalog to choose another pattern.
Install by copying sourceLink to section
- Copy the complete
audit-logdirectory into an application-owned path. - Configure React,
@pycolors/ui, tokens and semantic styles through the UI installation guide. - Supply stable event identities, timestamps, labels and consumer-owned actions.
- Run lint, type-check, tests and build in the consuming application.
"use client";
import { Button } from "@pycolors/ui";
import { AuditLogPanel } from "./blocks/audit-log";
export function WorkspaceAuditLog() {
return (
<AuditLogPanel
id="workspace-audit-log"
heading="Recent activity"
description="Security and workspace events supplied by the application."
events={[
{
id: "event-1",
actor: "Alex Morgan",
action: "updated",
target: "workspace settings",
occurredAt: "2026-09-15T08:30:00Z",
occurredAtLabel: "15 September 2026, 08:30 UTC",
details: "Example event metadata owned by the application.",
actions: (
<Button type="button" variant="outline">
View details
</Button>
),
},
]}
/>
);
}The button above is intentionally unconnected example UI. Your application owns its handler, destination, authorization and resulting behavior.
Data and permissions remain yoursLink to section
id, heading and events are required. Use a stable, unique HTML id for
each panel. Every event needs a stable id, actor, action, machine-readable
occurredAt value and consumer-formatted occurredAtLabel.
target, details and per-event actions are optional. The Block renders
values in the order supplied; it does not sort, filter, redact, format timestamps
or infer event severity. Supply already-authorized, display-ready data.
filters and top-level actions are arbitrary React nodes. They can host native
controls or public PyColors UI components, but the Block never reads their state,
changes a query or fetches results. Keep filtering and pagination logic in the
consumer when an audit history grows beyond a bounded list.
Do not use visible UI state as an authorization boundary. Check workspace roles, audit-log permissions and resource scope on trusted application/backend paths before returning events or performing actions.
Empty, responsive and accessible statesLink to section
With events={[]}, the Block renders emptyTitle and optional
emptyDescription. A successful empty result is not a loading or error state;
render those states explicitly in the application when required.
The section is labelled by its heading, events use an ordered list, and each
machine timestamp is rendered with a semantic <time dateTime> value. Event
text, details and actions wrap on narrow layouts, while preformatted detail
content may scroll horizontally within its bounded area.
The Block does not move focus or intercept keyboard behavior. Consumer controls keep their native focus and disabled semantics. Give repeated actions distinct accessible names when their visible labels would otherwise be ambiguous.
Ownership and operational boundariesLink to section
Your application owns event production, storage, immutability guarantees, retention, export, search, pagination, permissions, redaction and incident response. The Block does not provide audit compliance by itself.
After copying, review future canonical changes deliberately and preserve local customizations. Validate long actors/actions/targets, empty states, narrow widths, both themes, keyboard focus and real permission combinations before production.
No audit ingestion, database/schema, telemetry pipeline, authentication, authorization, backend/network request, Registry or CLI behavior is introduced by this Block or guide.