VisorVisor
ComponentsAdmin

Activity Feed

Admin activity feed compound — vertical ordered list of timestamped events for dashboards, audit logs, and notification views. ActivityFeed + ActivityFeedItem with leading, title, description, actor, timestamp, and trailing slots. Default, compact, and timeline variants. Built with CSS Modules and React Context — copy it into your project and own it completely.

Basic

  1. Profile published
    2m ago
  2. Profile edited
    5m ago
  3. Profile archived
    1h ago

With Description and Actor

  1. Profile publishedJustin

    All checks passed and the profile is now live.

    2m ago
  2. Profile editedAlex

    Updated bio and avatar photo.

    5m ago

With Trailing Meta

  1. Profile publishedJustin
    Live
    2m ago
  2. Import failedSystem
    Error
    10m ago

Compact Variant

  1. user.loginjustin@example.com
    2m ago
  2. record.updatealex@example.com
    5m ago
  3. record.deletesystem
    12m ago

Timeline Variant

  1. Build started
    10m ago
  2. Tests passed
    7m ago
  3. Deployed to staging
    5m ago
  4. Promoted to production
    just now

Installation

npx visor add activity-feed

This copies two files into your project:

  • components/ui/activity-feed/activity-feed.tsx — the component
  • components/ui/activity-feed/activity-feed.module.css — the styles

Usage

import {
  ActivityFeed,
  ActivityFeedItem,
} from '@/components/ui/activity-feed/activity-feed';

export default function Example() {
  return (
    <ActivityFeed aria-label="Recent activity">
      <ActivityFeedItem
        title="Profile published"
        actor="Justin"
        timestamp="2m ago"
      />
      <ActivityFeedItem
        title="Profile edited"
        description="Updated bio and avatar"
        actor="Justin"
        timestamp="5m ago"
      />
    </ActivityFeed>
  );
}

ActivityFeed.Item is also available as a dot-notation alias for ActivityFeedItem.

API Reference

ActivityFeedProps

PropTypeDefaultDescription
variant'default' | 'compact' | 'timeline''default'Display mode. Default is padded rows with borders, compact is denser with smaller text, timeline adds a connecting rail between leading indicators.
childrenReact.ReactNodeActivityFeedItem children.
classNamestringAdditional CSS class names to merge onto the root <ol>.
...propsReact.HTMLAttributes<HTMLOListElement>All standard HTML attributes are forwarded to the root <ol>.

The component also accepts all standard HTML attributes for the root <ol>.

ActivityFeedItemProps

PropTypeDefaultDescription
title*React.ReactNodePrimary event description.
timestamp*React.ReactNodePre-formatted timestamp. Pass a string, or pass your own <time> element for full control over dateTime semantics.
leadingReact.ReactNodeLeading visual — icon, avatar, or colored dot.
descriptionReact.ReactNodeOptional secondary detail beneath the title.
actorReact.ReactNodeWho performed the event — a name string, or an avatar + name cluster.
trailingReact.ReactNodeRight-aligned meta slot — status badge, link, etc.
classNamestringAdditional CSS class names to merge onto the <li>.
...propsReact.LiHTMLAttributes<HTMLLIElement>All standard HTML attributes are forwarded to the <li>.

The component also accepts all standard HTML attributes for the <li>.

Source Files

After running npx visor add activity-feed, you'll have:

activity-feed.tsx

A compound component pair. ActivityFeed is a forwardRef <ol> that exposes a React Context carrying the active variant. ActivityFeedItem is a forwardRef <li> that reads the context and adapts its layout — keeping the API ergonomic without forcing consumers to thread the variant prop through every child. ActivityFeed.Item is attached as a dot-notation alias for consumers who prefer that style.

activity-feed.module.css

All values reference CSS custom properties from @loworbitstudio/visor-core, so the feed adapts to your active theme. The timeline variant renders a connecting rail between leading indicators via a ::before pseudo-element on the leading slot; the last item's rail is hidden with a :last-child selector so the line doesn't extend past the final event.

Customization

After copying the component, you own it completely. Common customizations:

  • Add a grouped ActivityFeedGroup sub-component with a date heading for "Today / Yesterday / Last week" sectioning.
  • Add a color prop to items that tints the leading slot and the timeline rail (e.g., success/warning/error).
  • Make items keyboard-focusable and clickable for drilling into event detail — wrap the body in an <a> or <button> and add focus rings via var(--focus-ring-width) and var(--focus-ring-offset).