Skip to content
WingoUI
ComponentsTemplatesDocsPricingAI agentsBlogChangelogContact
Components
Buttons & Actions
ButtonButton GroupCopy ButtonDelete ButtonTheme ToggleToggleToggle Group
Inputs
Avatar UploadCalendarCheckboxColor PickerComboboxCountry SelectCron PickerCurrency InputDate PickerDate Range PickerDate Time PickerDuration PickerEmail InputEmoji PickerFile UploadFilter BuilderInputInput GroupListboxMasked InputMention InputMonth PickerMulti SelectNumber InputOTP InputPassword InputPhone InputRadio GroupRatingSearch InputSegmented ControlSelectSignature PadSliderSwitchTag InputTextareaTime PickerTimezone SelectTree Select
Forms
Address InputBooking WidgetCSV ImportFieldForm WizardInline EditInvoice line itemsRich Text EditorSchema Builder
Navigation
Back To TopBounce SidebarBreadcrumbCommand MenuGlobal SearchGooey NavHook SidebarNav MenuNav TabsPaginationProximity SidebarScroll ProgressSidebarStepsTable Of ContentsTabsTopbarUser MenuWorkspace Switcher
Overlays
Alert DialogContext MenuDialogDrawerDropdown MenuFamily DrawerHover CardPopoverShare DialogSheetShortcuts DialogTooltipTour
Feedback
AlertBannerCookie ConsentEmoji ReactionEmpty StateNotification BellNotification CenterProgressRoute ProgressSetup ChecklistSkeletonSpinnerToast
Data Display
Approval FlowAvatarBadgeCalendar ViewCode BlockComment ThreadContact cardCountdownDescription ListDiff ViewerFile ListFile managerFolderGanttJSON ViewerKbdLog ViewerMarkdownPresenceSchedulerTagTimelineTree View
Tables & Lists
Audit LogData TableDeal PipelineEditable GridKanbanListMembers tablePermissions MatrixTableTask ListVirtual List
Charts & Stats
Animated CounterArea chartBar chartDonut chartFunnel chartGaugeGitHub ActivityHeatmap calendarLeaderboardLine chartRadar chartRadial barSparklineStat CardTrackerTrend badge
Layout
AccordionApp ShellCardCollapsibleDetail LayoutEntity HeaderPage HeaderScroll AreaSeparatorSettings LayoutSplit ViewToolbar
Media
Audio PlayerCarouselCSV ViewerDOCX EditorDOCX ViewerExcel ViewerFile SystemImage compareImage CropperImage GalleryImage LightboxLocation MapPDF ViewerPowerPoint ViewerQR CodeStep PlayerVideo Player
AI Kit
Agent Status CardChat MessageChat ThreadChat WidgetConversation ListFluid OrbGrid RevealMatrix OrbModel PickerPrompt InputReasoning IndicatorRule BuilderSuggestion ChipsTool Call TimelineUsage MeterVoice InputWorkflow Canvas
Text & Effects
Background PatternGravity LettersMarqueeTerminalText AnimateText Rotate
Mobile
Action SheetBottom Tab BarFABHaptic ButtonHero HeaderInstall PromptLarge Title HeaderNotification BannerOnboarding CarouselPicker WheelPull To RefreshSlide To ConfirmSticky CTA BarSwipe DeckSwipeable Tabs
Commerce
Cart drawerCategory pickerCheckout stepsCoupon inputFavorite buttonFilter panelListing cardListing gridOrder trackerPricePrice rangePricing tableProduct galleryQuantity stepperReviewsSeller card
Marketing Sections
App Download SectionBlog Cards SectionCTA SectionFAQ SectionFeature GridFeature ShowcaseHero SectionIntegrations SectionLogo CloudMarketing FooterMarketing HeaderMarketing SectionNewsletter SectionPricing SectionStats SectionSteps SectionTeam SectionTestimonials SectionTimeline SectionVideo Section
Blocks
Admin Dashboard PageAdmin ShellAI Chat PageAPI Keys PageAutomation Builder PageCheckout PageCRM Contact PageCRM Pipeline PageError PagesInbox PageInvoice PageKPI DashboardListing Detail PageLogin PageMarketplace Home PageMarketplace Search PageMarketplace ShellMobile App HomeOnboarding FlowOrder History PagePost Listing WizardPricing PageProfile PageRegister PageSettings PageUsers Admin Page
Templates
About PageAcademy PageAccessibility PageAffiliate PageAgency PageAI Product PageAppointment Booking PageBlog PageBlog Post PageBug Bounty PageCareers PageChangelog PageCommunity PageCompare PageContact PageCustomer Story PageCustomers PageDemo Booking PageDevelopers PageDocs Home PageDownload PageEnterprise PageEvents PageExperts PageFAQ PageFeature PageGlossary PageHelp Article PageHelp Center PageHome Services PageImpact PageIntegration Detail PageIntegrations PageJob Detail PageLanding PageLaunch PageLegal PageManifesto PagePortfolio PagePress PageProduct Tour PagePromo PageReal Estate PageReferral PageResearch Report PageRestaurant PageRoadmap PageROI Calculator PageSecurity PageSolutions PageStartups PageStatus PageStorefront PageSwitch PageTemplate Gallery PageTravel PageWaitlist PageYear in Review Page
Hooks & Utilities
AI ChatChart kitCommerceDate utilsFile exportFile typesFiltersForm validationHapticsInvoicingMotion presetsNavigationNumber formatRelative timeRomanian locationsSpreadsheetText matchTonesUIProvideruseClipboarduseConsentuseControllableStateuseCooldownuseDebounceuseKeyboardInsetuseLocaleuseLongPressuseMediaControlsuseMediaQueryuseOverflowItemsusePersistedStateuseScrollEdgesuseScrollStateuseSelectionuseShortcutuseStickToBottom
GalleryTheme

Tables & Lists

Audit Log

Tables & Lists

Audit Log React component for Tailwind CSS v4

Tree ViewData Table
Live props
Behavior
mode
sortOrder
expandable
exportable
exportFileName
csvDelimiter
autoLoadMore
maxRows200
State
defaultSearch
loading
skeletonCount8
refreshing
error
loadingMore
Layout
groupByDay
showIp
showSeverity
searchable
filterable
showPeriod
Appearance
timeFormat
density
radius
tone
Content
currency
locale
Code
import { AuditLog } from "@/components/ui/audit-log"

<AuditLog />
  1. Home
  2. Components
  3. Tables & Lists
  4. Audit Log
Pro

Audit Log React component for Tailwind CSS v4

v1.0.0·Added in 1.0.0

A React audit log table: who did what, when and to which record, with filters, day groups, before / after diffs and a CSV export.

Search the log (actor, sentence, record and IP match, accents ignored), pick a period with quick ranges (Today, Last 7 days, Last 30 days, This month) and add filter chips for user, action, record type, severity or IP; the count updates and is announced. On desktop each event is a table row under a sticky day heading: click the row or its chevron button (Enter / Space) to open the before / after diff and the technical details, where IP, request id and metadata copy on click. The record in the sentence is a link. Tab reaches the toggles, links and copy buttons in order. Export CSV downloads exactly the events on screen (disabled when there are none). Below 768px the table becomes a feed of day groups: tap a row to expand it inline, the record link moves into the details, the period and filters open as bottom sheets, and Load more is a full-width button (or loads on scroll with autoLoadMore). Rows added by Load more slide in; the first page never animates. With reduced motion the rows and details appear without movement.

Audit Log is a Pro React component in the React table and list components category, written in TypeScript for React 19 and Tailwind CSS v4. It installs as source with npx wingo-ui@latest add audit-log, together with Avatar, Badge, Button, Copy Button, Date Range Picker and 18 more and the npm packages motion and lucide-react.

Add to a project

Requirements: React 19 and Tailwind CSS v4, in Next.js (App Router or Pages), Vite or React Router. Installation guide · Next.js setup · Vite setup

npx wingo-ui@latest add audit-log

Audit Log is part of Pro, with 298 more Pro items: $8 a month or $150 once. 14-day refund before you download any Pro item. See plans

Usage

Import AuditLog from @/components/ui/audit-log.

import { FilePlus2, FileX2, Pencil } from "lucide-react"
import { AuditLog, type AuditActionDef, type AuditEvent } from "@/components/ui/audit-log"

const actions: Record<string, AuditActionDef> = {
  "invoice.issued": { label: "issued", icon: <FilePlus2 />, tone: "info", group: "Invoices" },
  "invoice.cancelled": { label: "voided", icon: <FileX2 />, tone: "warning", group: "Invoices" },
  "client.updated": { label: "updated", icon: <Pencil />, group: "Clients" },
}

const events: AuditEvent[] = [
  {
    id: "e-1",
    at: "2026-09-24T16:35:00",
    actor: { id: "jessica", name: "Jessica Dunn", avatar: "/avatars/jessica.jpg" },
    action: "invoice.cancelled",
    target: { type: "invoice", id: "2026-0398", label: "Invoice #2026-0398", href: "/invoices/2026-0398" },
    severity: "warning",
    changes: [{ field: "status", label: "Status", type: "status", from: { label: "Issued", tone: "info" }, to: { label: "Voided", tone: "danger" } }],
    ip: "93.114.52.211",
    device: "Firefox · Windows",
  },
  {
    id: "e-2",
    at: "2026-09-24T16:12:00",
    actor: { id: "anna", name: "Anna Price" },
    action: "client.updated",
    target: { type: "client", id: "c04", label: "Greenleaf Pharmacy" },
    changes: [{ field: "balance", label: "Balance", type: "money", from: 7315.2, to: 0 }],
  },
]

export function ActivityLog() {
  return (
    <AuditLog
      events={events}
      actions={actions}
      targetTypes={{ invoice: "Invoice", client: "Client" }}
      currency="USD"
      exportFileName="activity-log"
      labels={{ search: "Search activity", export: "Download CSV" }}
    />
  )
}

Props

  • events*
    AuditEvent[]

    The events, newest first ({ id, at, actor: { id, name, avatar?, kind? }, action, target?: { type, id, label, href? }, summary?, changes?, severity?, ip?, device?, location?, requestId?, metadata? }). Not sorted unless sortOrder says so.

  • actions
    Record<string, AuditActionDef>

    Labels, icons, tones and groups per action key: { "invoice.cancelled": { label: "voided", icon, tone: "warning", group: "Invoices" } }. An unknown key prints the key.

  • targetTypes
    Record<string, string>

    Record type names for the filter and the export: { invoice: "Invoice" }.

  • modedefault "client"
    "client" | "server"

    client filters, searches and limits the period here. server leaves the events alone and only calls onQueryChange; pass the result and totalCount.

  • onQueryChange
    (query: { search: string; period: DateRange | null; filter: FilterGroup }) => void

    Called 300ms after the search, the period or the filter last changed (never on mount).

  • defaultSearchdefault ""
    string

    The search the log starts with. Use search / onSearchChange to control it.

  • search
    string

    The search, controlled.

  • onSearchChange
    (search: string) => void

    Every keystroke of the search.

  • filter
    FilterGroup

    The filter, controlled (a lib/filters group).

  • defaultFilter
    FilterGroup

    The filter the log starts with; an empty AND group by default.

  • onFilterChange
    (filter: FilterGroup) => void

    Every change of the filter chips.

  • filterFields
    FilterBuilderField<AuditEvent>[]

    The filterable fields. Default defaultAuditFilterFields(actions, the events' actors): user, action, record type, severity, IP.

  • views
    FilterView[]

    Saved views in the builder's menu ("Deletions only", "Critical actions").

  • period
    DateRange | null

    The period, controlled.

  • defaultPeriod
    DateRange | null

    The period the log starts with; null shows everything.

  • onPeriodChange
    (period: DateRange | null) => void

    A picked range, or null when cleared.

  • groupByDaydefault true
    boolean

    Day headings ("Today", "Yesterday", "Monday, September 21") that stick under the table header. Events out of order land under their own day.

  • sortOrderdefault "none"
    "desc" | "asc" | "none"

    none keeps the order you pass; desc and asc sort by time.

  • expandabledefault true
    boolean

    Rows with changes or details expand into the before / after diff and the IP, device, location and request id.

  • expandedKeys
    string[]

    The expanded event ids, controlled.

  • defaultExpandedKeys
    string[]

    Event ids expanded at first.

  • onExpandedKeysChange
    (keys: string[]) => void

    A row was opened or closed.

  • timeFormatdefault "relative"
    "relative" | "time" | "datetime"

    relative: "5 min ago" today and the time under older day headings. time: the clock (with the date when not grouped). datetime: date and time.

  • showIpdefault true
    boolean

    The IP column on desktop. The IP stays in the details either way.

  • showSeveritydefault true
    boolean

    The severity column: notice, warning and critical as soft badges with an icon; info stays quiet.

  • searchabledefault true
    boolean

    The search field at the start of the toolbar.

  • filterabledefault true
    boolean

    The filter builder: chips on desktop, a Filters button with a bottom sheet on phones.

  • showPerioddefault true
    boolean

    The period picker with quick ranges.

  • exportabledefault true
    boolean

    The export button: onExport, or a CSV download of the events on screen. Disabled with no events.

  • onExport
    (events: AuditEvent[], query: AuditLogQuery) => (void | Promise<unknown>)

    Replaces the built-in CSV (a server export). A returned promise shows the button loading; a rejection is announced.

  • exportFileNamedefault "audit-log"
    string

    The CSV name before the date: "audit-log-2026-09-24.csv".

  • csvDelimiterdefault ","
    "," | ";"

    The CSV separator. ";" opens straight in Excel set to a European locale, which reads "," as the decimal mark.

  • onEventClick
    (event: AuditEvent) => void

    The record link's click when the target has no href.

  • renderSummary
    (event: AuditEvent, parts: { actor: ReactNode; verb: ReactNode; target: ReactNode }) => ReactNode

    A custom sentence from the three parts. event.summary replaces the sentence for one event.

  • densitydefault "default"
    "compact" | "default"

    Row height: 48px with a second line for times and actor kinds, or 36px on one line.

  • radiusdefault "md"
    "none" | "sm" | "md" | "lg" | "full"

    Corners of the search, the period, the export and load-more buttons.

  • tonedefault "neutral"
    "neutral" | "brand" | "success" | "warning" | "danger" | "info"

    The active filter chips and the period's selection.

  • loadingdefault false
    boolean

    Skeleton rows with the real shape (feed rows on phones) in place of the events.

  • skeletonCountdefault 8
    number

    How many skeleton rows loading shows (phones show at most 6).

  • refreshingdefault false
    boolean

    Keeps the rows and shows a thin indeterminate bar at the top while a request runs.

  • errordefault ""
    ReactNode

    Replaces the rows with an error state and a Try again button (onRetry). Text or a node is the message; true shows labels.error.

  • onRetry
    () => (void | Promise<unknown>)

    The error state's Try again; a promise shows it loading.

  • emptyState
    ReactNode

    Replaces the empty state when there are no events at all.

  • hasMoredefault false
    boolean

    Shows Load more under the rows. Big logs page on the server with it.

  • onLoadMore
    () => (void | Promise<unknown>)

    Loads the next page; a returned promise keeps the button loading.

  • loadingMoredefault false
    boolean

    Shows Load more in its loading state (for a request started elsewhere).

  • autoLoadMoredefault false
    boolean

    Loads the next page when the Load more button scrolls within 200px of view.

  • maxRowsdefault 200
    number

    Renders at most this many events (0 renders all) and says so under the list. Beyond a few hundred, page on the server with hasMore.

  • totalCount
    number

    The count in the toolbar when the server knows the total (server mode).

  • currencydefault "USD"
    string

    ISO 4217 code for money changes without their own currency.

  • now
    Date | number

    The reference time for relative times and day headings. Default: the current minute (useNow).

  • localedefault ""
    string

    Dates, times, numbers and money. Empty uses the UIProvider locale, then the browser's.

  • linkComponent
    (props: LinkComponentProps) => ReactNode

    The router link for record links (default the navigation provider's, else <a>).

  • labels
    Partial<AuditLogLabels>

    Every string, English by default (DEFAULT_AUDIT_LOG_LABELS): search, period, export, count(n, formatted), column names, severities, expand / collapse, emptyValue, fieldCount, rangePresets, csvHeaders and more.

  • filterBuilderProps
    Partial<FilterBuilderProps<AuditEvent>>

    Extra props for the filter builder: its labels, applyMode, onSaveView, defaultActiveViewId.

  • dateRangePickerProps
    Partial<DateRangePickerProps>

    Extra props for the period picker: its labels, min, numberOfMonths, your own presets.

  • classNames
    Partial<Record<"toolbar" | "search" | "period" | "filters" | "export" | "count" | "refreshing" | "table" | "group" | "row" | "details" | "diff" | "meta" | "feed" | "item" | "more" | "empty" | "error", string>>

    Extra classes for the inner parts.

  • className
    string

    Extra classes for the root. Set --table-sticky-top here to sit the sticky header under an app bar.

Parts

  • AuditDiff

    component

    The before / after table alone: values formatted by type (money, number, date, boolean, status badge, list), empty values muted. auto stacks the rows below 448px of room. Props: { changes: AuditChange[]; currency?: string; locale?: string; layout?: "auto" | "table" | "stack"; labels?; classNames? }.

  • defaultAuditFilterFields

    function

    The default filter fields: user, action, record type, severity and IP, for your own builder or a server that filters with lib/filters. Signature: (actions, actors, options?: { labels?; targetTypes?; types? }) => FilterBuilderField<AuditEvent>[].

  • auditEventsToCsv

    function

    The export's rows (header first) for toCsv / downloadCsv from lib/spreadsheet. Signature: (events: AuditEvent[], options?: { actions?; labels?; targetTypes?; locale?; currency?; header? }) => (string | null)[][].

  • formatAuditValue

    function

    A change value as text, the way the diff prints it. Signature: (value: unknown, change: AuditChange, context: { locale; currency; labels }) => string.

  • visibleChanges

    function

    The changes with a from or a to (the ones the diff shows). Signature: (changes?: AuditChange[]) => AuditChange[].

  • DEFAULT_AUDIT_LOG_LABELS

    constant

    The English strings, to spread under your translations. Type: AuditLogLabels.

Examples

Phone feed

At 390px the table becomes a day-grouped feed; tap a row to open its changes.

Server mode

Search, period and filters go to a fake endpoint with 600ms latency.

AuditDiff

The before / after table of a client edit, on its own.

Saved views

Built-in views for deletions, critical actions and integrations.

States

Loading skeletons, an error with retry and a search with no results.

Compact

Dense rows, full timestamps and no IP column.

Dependencies

motionlucide-reactavatarbadgebuttoncopy-buttondate-range-pickerempty-statefilter-builderprogresssearch-inputtableuse-controllable-stateuse-localecommercedate-utilsfiltersmotionnavigationnumber-formatrelative-timespreadsheettext-matchtonesui-config

Files

  • components/ui/audit-log.tsx

FAQ

Is the React Audit Log component free?

The preview, props and docs are free; copying the source needs a Pro plan. See plans.

How do I install Audit Log?

Run npx wingo-ui@latest add audit-log. It also adds the Wingo UI items it builds on (Avatar, Badge, Button, Copy Button, Date Range Picker and 18 more) and the npm packages motion and lucide-react. With shadcn, add the @wingo-ui registry with your API key, then run npx shadcn@latest add @wingo-ui/audit-log.

How does Audit Log behave on phones?

Below 768px the table becomes a feed of day groups: tap a row to expand it inline, the record link moves into the details, the period and filters open as bottom sheets, and Load more is a full-width button (or loads on scroll with autoLoadMore).

See also

Filter BuilderTableTimelineSpreadsheetlibFilterslib

Used in

Users Admin Page

More React table and list components

Editable GridListMembers tablePermissions MatrixTask ListVirtual List
All 11 React table and list components

Articles using this component

  • Audit Log UI in React: Filters, Diffs and CSV Export

    Component guides · Nov 6, 2026 · 10 min read

  • Role Management UI in React: Members, Roles, Audit Log

    Build it: screens and apps · Nov 5, 2026 · 9 min read

  • React Diff Viewer in React 19: Split, Unified, Word Diffs

    Component guides · Oct 16, 2026 · 9 min read

PreviousTree ViewNext Data Table
40 results40 results
40 events
Activity log
DetailsTimeUserActionSeverityIP
Today
5 min. ago4:35 PMJessica DunnJessica Dunn voided Warning93.114.52.211
28 min. ago4:12 PMAnna PriceAnna Price updated 86.124.52.18

Changes

FieldBeforeAfter
Balance$7,315.20$0
StatusOverdueActive
OwnerRyan MartinAnna Price
Balance
Before: $7,315.20After: $0
Status
Before: OverdueAfter: Active
Owner
Before: Ryan MartinAfter: Anna Price
IP
Device
Chrome · macOS
Location
San Francisco, US
Request
42 min. ago3:58 PMQuickBooks OnlineIntegrationQuickBooks Online synced to QuickBooks 194.102.170.12
1 hr. ago3:31 PMLiam BrooksLiam Brooks changed the role of Notice79.115.62.40
2 hr. ago2:47 PMSystemSystem blocked a sign-in for Warning86.124.17.203
3 hr. ago2:05 PMMichael HayesMichael Hayes moved 5.2.144.18
3 hr. ago1:31 PMMichael HayesMichael Hayes issued 5.2.144.18
3 hr. ago1:15 PMQuickBooks OnlineIntegrationQuickBooks Online synced to QuickBooks 194.102.170.12
7 hr. ago9:34 AMRyan MartinRyan Martin updated 188.26.93.7
8 hr. ago8:49 AMJessica DunnJessica Dunn moved 93.114.52.211
Yesterday
11:05 PMAndrew PorterAndrew Porter issued 82.78.14.96
8:15 PMQuickBooks OnlineIntegrationQuickBooks Online synced to QuickBooks 194.102.170.12
8:10 PMSystemSystem blocked a sign-in for Warning86.124.17.203
6:30 PMMaria ThomasMaria Thomas updated 86.120.41.77
5:28 PMEllen StoneEllen Stone updated 109.166.130.4
4:38 PMMichael HayesMichael Hayes added the client 5.2.144.18
3:48 PMLiam BrooksLiam Brooks changed the role of Notice79.115.62.40
2:42 PMAndrew PorterAndrew Porter issued 82.78.14.96
11:45 AMAnna PriceAnna Price deleted the client Warning86.124.52.18
11:26 AMMaria ThomasMaria Thomas issued 86.120.41.77
10:57 AMAnna PriceAnna Price updated 86.124.52.18
8:51 AMAndrew PorterAndrew Porter updated 82.78.14.96
8:19 AMRyan MartinRyan Martin added the client 188.26.93.7
Tuesday, September 22
7:21 PMMaria ThomasMaria Thomas issued 86.120.41.77
5:07 PMAndrew PorterAndrew Porter issued 82.78.14.96
3:52 PMAndrew PorterAndrew Porter updated 82.78.14.96
2:39 PMAndrew PorterAndrew Porter issued 82.78.14.96
2:38 PMQuickBooks OnlineIntegrationQuickBooks Online synced to QuickBooks 194.102.170.12
1:20 PMJessica DunnJessica Dunn moved 93.114.52.211
12:12 PMRyan MartinRyan Martin updated 188.26.93.7
8:57 AMAndrew PorterAndrew Porter deleted the client Warning82.78.14.96
Monday, September 21
8:42 PMRyan MartinRyan Martin moved 188.26.93.7
8:20 PMQuickBooks OnlineIntegrationQuickBooks Online synced to QuickBooks 194.102.170.12
6:46 PMEllen StoneEllen Stone voided Warning109.166.130.4
4:19 PMAnna PriceAnna Price updated 86.124.52.18
10:54 AMRyan MartinRyan Martin exported the clientsWarning188.26.93.7
9:43 AMAnna PriceAnna Price updated 86.124.52.18
9:33 AMMaria ThomasMaria Thomas updated 86.120.41.77
8:20 AMLiam BrooksLiam Brooks changed the role of Notice79.115.62.40
Saturday, September 19
5:46 PMAnna PriceAnna Price updated 86.124.52.18
40 events

Today

  • Open:

    Changes

    FieldBeforeAfter
    Balance$7,315.20$0
    StatusOverdueActive
    OwnerRyan MartinAnna Price
    Balance
    Before: $7,315.20After: $0
    Status
    Before: OverdueAfter: Active
    Owner
    Before: Ryan MartinAfter: Anna Price
    IP
    Device
    Chrome · macOS
    Location
    San Francisco, US
    Request

Yesterday

Tuesday, September 22

Monday, September 21

Saturday, September 19

Click a row to see what changed. Filter by user or severity, pick a period, then export exactly what is on screen.

mode: client · autoLoadMore: false · exportFileName: "audit-log" · csvDelimiter: ","