Log Viewer React component for Tailwind CSS v4
v1.0.0Added in 1.0.0
A React log viewer for streaming logs up to 100,000 lines: level colors, level and regex filters, tail follow, ANSI colors and JSON details.
New lines stream in at the bottom and the view follows them. Scroll up (wheel, finger, scrollbar or keys) and following pauses by itself: a pill at the bottom counts the new lines (12 new lines) and a tap on it, the End key or scrolling back to the very end follows again; the follow toggle and the Live / Paused badge in the footer show the state. Type in the search to hide the lines that do not match (case and diacritics are ignored), or turn on the .* toggle for a regular expression (an invalid one is marked and filters nothing); in highlight mode every line stays, hits are marked and Enter / Shift+Enter or the arrows step through them. The level chips turn levels on and off and show their counts. Click, tap or press Enter on a line with details to open its JSON tree under it (the stream pauses so it stays put). The toolbar wraps lines, shows or hides the time, and the ⋯ menu copies the visible lines, downloads everything as a dated .log file or clears the buffer; on a narrow viewer wrap and time move into that menu, and below 768px it opens as a bottom sheet while the chips scroll sideways. The log body is one Tab stop: Up and Down move a focus row, Page Up / Page Down jump a page, Home goes to the first line, End to the newest, Escape drops the focus row (screen readers hear each focus row as it moves), and Ctrl+F / ⌘F inside the viewer jumps to its search (searchShortcut adds a global key). Lines over 10.000 characters are cut with a Show all button; with maxLines reached the oldest lines drop and the footer says how many.
Log Viewer is a Pro React component in the React data display components category, written in TypeScript for React 19 and Tailwind CSS v4. It installs as source with npx wingo-ui@latest add log-viewer, together with Badge, Button, Dropdown Menu, Empty State, JSON Viewer and 16 more and the npm packages @tanstack/react-virtual, 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 log-viewerLog Viewer 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 LogViewer from @/components/ui/log-viewer.
import { useEffect, useState } from "react"
import { LogViewer, type LogLine, type LogViewerApi } from "@/components/ui/log-viewer"
const initial: LogLine[] = [
{ time: "2026-09-24T13:38:12.345Z", level: "info", source: "sync", message: "Fetching messages for tax ID 12-3456740" },
{ time: "2026-09-24T13:38:12.902Z", level: "warn", source: "sync", message: "Invoice 2026-0409: slow response, retrying (2/5)" },
{
time: "2026-09-24T13:38:13.410Z",
level: "error",
source: "validator",
message: "Validation error: BR-CO-17 tax amount differs",
meta: { invoice: "2026-0411", code: "BR-CO-17" },
},
]
export function SyncJobLog({ events }: { events: EventSource }) {
const [api, setApi] = useState<LogViewerApi | null>(null)
useEffect(() => {
if (!api) return
const onMessage = (event: MessageEvent<string>) => api.append(event.data)
events.addEventListener("message", onMessage)
return () => events.removeEventListener("message", onMessage)
}, [api, events])
return (
<LogViewer
lines={initial}
setApi={setApi}
fileName="invoices-4812"
searchShortcut="mod+shift+f"
locale="en-US"
labels={{ search: "Filter the log" }}
/>
)
}Props
linesdefault"[]"LogLine[] | string[]The lines: { id?, time?, level?, message, source?, meta? } or raw strings (parsed with parseLogLine). Pass a new array to append: only the new tail is read; a shorter or different array starts over. For high rates use setApi().append, which never re-renders the parent.
maxLinesdefault100000numberA ring buffer: past it the oldest lines drop and the footer says how many. Lowering it at runtime trims from the start.
levelsdefault"["trace", "debug", "info", "warn", "error", "fatal"]"LogLevel[]The level chips shown in the toolbar ("trace", "debug", "info", "warn", "error", "fatal").
levelFilterLogLevel[]Controlled visible levels. Lines without a level are always shown.
defaultLevelFilterdefault"["trace", "debug", "info", "warn", "error", "fatal"]"LogLevel[]Visible levels on mount; every level by default.
onLevelFilterChange(levels: LogLevel[]) => voidA level chip was turned on or off.
querystringControlled search text.
defaultQuerydefault""stringThe search on mount. Words match anywhere in the message or source, ignoring case and diacritics; the filter runs 120ms after typing stops.
onQueryChange(query: string) => voidEvery keystroke in the search.
filterModedefault"filter""filter" | "highlight"filter hides the lines that do not match; highlight keeps them, marks the hits and steps through them with Enter / Shift+Enter.
regexdefaultfalsebooleanThe search is a regular expression (case-insensitive). The .* toggle in the toolbar flips it; an invalid expression is marked and filters nothing.
followbooleanControlled following of the newest line.
defaultFollowdefaulttruebooleanStick to the newest line on mount. Scrolling up pauses it by itself.
onFollowChange(follow: boolean) => voidFollowing paused (scroll up, a key, an opened line) or resumed (the pill, End, the toggle, scrolling to the end).
wrapboolean | "auto"Controlled line wrapping.
defaultWrapdefault"auto"boolean | "auto"auto wraps below 768px and keeps one line (scrolling sideways) above.
onWrapChange(wrap: boolean) => voidThe wrap toggle was pressed.
showTimestampsdefaulttruebooleanThe time column. The toolbar clock toggle flips it too.
timeFormatdefault"time""time" | "datetime" | "relative" | "iso"time 16:38:12.345, datetime 24.09.2026, 16:38:12, relative 2 min ago (ticking), iso as stored. Local times print after hydration.
showLevelsdefaulttruebooleanThe 3-letter level tag (INF, WRN, ERR) in the level's tone.
showSourcedefaulttruebooleanThe source chip ([spv], [http]), truncated at 14 characters.
showLineNumbersdefaultfalsebooleanLine numbers that keep counting after old lines drop. They are skipped when copying text.
ansidefaulttruebooleanRenders ANSI colors (30 to 37, 90 to 97, backgrounds, bold, dim, italic, underline) with the tone palette; false strips them.
expandabledefaulttruebooleanLines with meta open a JSON tree under them on click, tap or Enter.
sizedefault"sm""sm" | "md"12px text on 24px rows or 13px on 28px rows.
heightdefault420numberHeight of the log body in px.
toolbardefaulttruebooleanThe row with search, level chips, toggles and the ⋯ menu.
searchShortcutdefault""stringA global key that focuses the search, like mod+shift+f (shown as a hint in the field). Empty binds nothing; Ctrl+F / ⌘F always works inside the viewer.
fileNamedefault"logs"stringDownload base name: logs-2026-09-24-1640.log.
loadingdefaultfalsebooleanSkeleton rows in place of the log; the toolbar waits.
emptyStateReactNodeReplaces the built-in No logs yet state.
nowDate | numberThe clock for relative timestamps. Defaults to the current time, ticking every second.
setApi(api: LogViewerApi) => voidCalled after mount with { append(lines), clear(), scrollToBottom(), follow(on), getLines() }. append takes a line, a string or arrays of either and batches them into one render per frame.
localedefault""stringCounts and times. Empty uses the UIProvider locale, then the browser's.
labelsPartial<LogViewerLabels>Every string: log, search, regex, invalidRegex, levels { trace, debug, info, warn, error, fatal }, levelsGroup, wrap, timestamps, follow, live, paused, newLines(count, formatted), jumpToLatest, copy, download, clear, moreActions, actions, cancel, lines(…), errors(…), filtered(…), dropped(…), matches(index, total), previousMatch, nextMatch, empty, emptyDescription, noMatches, noMatchesDescription, clearFilters, showAll(…), details, loading, copied, copyFailed, downloaded(fileName). Pass only the keys you translate.
classNamesPartial<Record<"toolbar" | "search" | "chips" | "chip" | "actions" | "body" | "row" | "number" | "time" | "level" | "source" | "message" | "meta" | "jump" | "empty" | "status", string>>Extra classes for the inner parts.
classNamestringExtra classes for the root panel, merged last.
Parts
DEFAULT_LOG_VIEWER_LABELS
constantThe English copy of the toolbar, filters, menus and announcements. Spread it into labels when you translate only a few keys.
LogLine
typeOne parsed line: message, plus optional id, time, level, source and meta (shown as JSON details).
parseLogLine
functionParses "2026-09-24T16:38:12.345Z INFO [spv] message", "[16:38:12] WARN message", "ERROR message" and JSON lines with level / msg / time (pino numeric levels too; the other keys become meta). Signature: (text: string) => LogLine.
stripAnsi
functionRemoves ANSI escape sequences (colors, cursor moves, links). Signature: (text: string) => string.
LOG_LEVELS
constantThe level recipe (tone, 3-letter tag, row tint, text class), for legends and custom rows. Type: Record<LogLevel, { tone: Tone; tag: string; row: string; text: string }>.
Examples
100,000 lines
Virtualized: only the rows on screen are in the DOM. Scroll, filter and watch the frame counter.
Warnings and errors only
The level chips filter with live counts; a controlled levelFilter.
Regex highlight
Highlight mode keeps every line and marks BR-\w+-\d+ hits; Enter and Shift+Enter step through them.
Build with ANSI colors
npm run build output: SGR colors mapped to the tone palette, line numbers, no wrap.
Parsed text lines
Plain strings and JSON lines parsed by parseLogLine; new lines are appended from a new array.
On a phone
Wrapped lines, a scrolling chip rail and the ⋯ menu that opens as a sheet below 768px.
Dependencies
Files
- components/ui/log-viewer.tsx
FAQ
Is the React Log Viewer component free?
The preview, props and docs are free; copying the source needs a Pro plan. See plans.
How do I install Log Viewer?
Run npx wingo-ui@latest add log-viewer. It also adds the Wingo UI items it builds on (Badge, Button, Dropdown Menu, Empty State, JSON Viewer and 16 more) and the npm packages @tanstack/react-virtual, motion and lucide-react. With shadcn, add the @wingo-ui registry with your API key, then run npx shadcn@latest add @wingo-ui/log-viewer.
How does Log Viewer behave on phones?
The toolbar wraps lines, shows or hides the time, and the ⋯ menu copies the visible lines, downloads everything as a dated .log file or clears the buffer; on a narrow viewer wrap and time move into that menu, and below 768px it opens as a bottom sheet while the chips scroll sideways.
See also
Used in
More React data display components
Articles using this component
- React Log Viewer: Stream 100,000 Lines With Regex Search
Component guides8 min read
- React Virtualized Table: 100k Rows With TanStack Virtual
Component guides12 min read