# Event Stream

> Frameless cycling JSON animationâ€”three webhook events type out in sequence with edge fade, no borders, no syntax colors, designed to embed in any section.

A shadcn/ui block from the [@uiception](https://uiception.com) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/uiception/event-stream).

- Page, with a live preview: https://shadcn.exchange/r/uiception/event-stream
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/uiception/event-stream-light-ecad53d7de758ed8.png
- Registry item: https://uiception.com/r/event-stream.json
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://uiception.com/r/event-stream.json
```

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

## Files

### `registry/new-york/blocks/event-stream/page.tsx`

```tsx
import EventStream from "./components/event-stream"

export default function Page() {
  return (
    <div className="px-4 py-10 md:px-6 md:py-14">
      <EventStream />
    </div>
  )
}
```

### `registry/new-york/blocks/event-stream/components/event-stream.tsx`

```tsx
"use client"

import { cn } from "@/lib/utils"

import { useEventAnimation } from "../hooks/use-event-animation"
import TokenText from "./token-text"

import "../styles/event-stream.css"

export default function EventStream({ className }: { className?: string }) {
  const { text, scrollRef } = useEventAnimation()

  return (
    <div
      className={cn(
        "es-root relative mx-auto w-full max-w-3xl min-w-0 overflow-hidden",
        className
      )}
      style={{ height: "18rem" }}
    >
      <div
        ref={scrollRef}
        className="flex h-full justify-center overflow-y-auto pt-4 pb-16 [-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
      >
        <pre className="m-0 w-fit font-mono text-[11px] leading-[1.45] whitespace-pre text-muted-foreground">
          <TokenText text={text} />
          <span className="es-cursor ml-px inline-block h-[1em] w-1.5 bg-muted-foreground align-bottom" />
        </pre>
      </div>
    </div>
  )
}
```

### `registry/new-york/blocks/event-stream/components/token-text.tsx`

```tsx
import { useMemo } from "react"

// Splits text into plain segments and JSON number-literal segments.
// Swap this component to add syntax colours, highlights, or diff markers.
function tokenize(text: string): Array<{ t: string; num: boolean }> {
  const out: Array<{ t: string; num: boolean }> = []
  const re = /-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?/g
  let last = 0
  let m: RegExpExecArray | null
  while ((m = re.exec(text)) !== null) {
    if (m.index > last) out.push({ t: text.slice(last, m.index), num: false })
    out.push({ t: m[0], num: true })
    last = m.index + m[0].length
  }
  if (last < text.length) out.push({ t: text.slice(last), num: false })
  return out
}

interface TokenTextProps {
  text: string
}

export default function TokenText({ text }: TokenTextProps) {
  const tokens = useMemo(() => tokenize(text), [text])

  return (
    <>
      {tokens.map((tok, i) =>
        tok.num ? (
          <span key={i} className="tabular-nums">
            {tok.t}
          </span>
        ) : (
          tok.t
        )
      )}
    </>
  )
}
```

### `registry/new-york/blocks/event-stream/hooks/use-event-animation.ts`

```ts
﻿import { useReducer, useLayoutEffect, useMemo, useRef } from "react"

import { EVENTS, TYPE_CHAR_MS, TYPE_NEWLINE_MS, VISIBLE_EVENT_BUFFER } from "../lib/config"

type AnimationState = {
  buffer: string[]
  eventIdx: number
  charIdx: number
}

type AnimationAction = { type: "advanceChar" } | { type: "completeEvent" }

function animationReducer(state: AnimationState, action: AnimationAction): AnimationState {
  switch (action.type) {
    case "advanceChar":
      return { ...state, charIdx: state.charIdx + 1 }
    case "completeEvent": {
      const content = EVENTS[state.eventIdx]
      return {
        buffer: [...state.buffer, content].slice(-VISIBLE_EVENT_BUFFER),
        charIdx: 0,
        eventIdx: (state.eventIdx + 1) % EVENTS.length,
      }
    }
    default:
      return state
  }
}

function buildDisplayText(buffer: string[], eventIdx: number, charIdx: number): string {
  const wip = EVENTS[eventIdx].slice(0, charIdx)
  if (buffer.length === 0) return wip
  return buffer.join("\n\n") + "\n\n" + wip
}

export function useEventAnimation() {
  const [{ buffer, eventIdx, charIdx }, dispatch] = useReducer(animationReducer, {
    buffer: [],
    eventIdx: 0,
    charIdx: 0,
  })
  const scrollRef = useRef<HTMLDivElement>(null)

  const text = useMemo(
    () => buildDisplayText(buffer, eventIdx, charIdx),
    [buffer, eventIdx, charIdx],
  )

  useLayoutEffect(() => {
    const el = scrollRef.current
    if (!el) return
    el.scrollTop = Math.max(0, el.scrollHeight - el.clientHeight)
  }, [text])

  useLayoutEffect(() => {
    const content = EVENTS[eventIdx]

    if (charIdx >= content.length) {
      dispatch({ type: "completeEvent" })
      return
    }

    const ch = content[charIdx]
    const delay = ch === "\n" ? TYPE_NEWLINE_MS : TYPE_CHAR_MS
    const t = setTimeout(() => dispatch({ type: "advanceChar" }), delay)
    return () => clearTimeout(t)
  }, [charIdx, eventIdx])

  return { text, scrollRef }
}
```

### `registry/new-york/blocks/event-stream/lib/config.ts`

```ts
export const TYPE_CHAR_MS = 16
export const TYPE_NEWLINE_MS = 38

/** Completed events kept in the sliding window; older ones drop off the top through the mask. */
export const VISIBLE_EVENT_BUFFER = 3

export const EVENTS = [
  `{
  "event": "order.created",
  "id": "evt_9x2k4mpl",
  "timestamp": "2024-03-15T10:22:07Z",
  "data": {
    "order_id": "ord_8f3k2x9p",
    "status": "confirmed",
    "customer": {
      "id": "cus_4m9xp1r",
      "name": "Steve Jobs",
      "email": "s.jobs@icloud.com"
    },
    "items": [
      {
        "id": "itm_3t7vk2nx",
        "name": "MacBook Pro 14-inch M4 Pro",
        "sku": "MBP14-M4P-16-512-SLV",
        "price": 1999.00,
        "quantity": 1
      },
      {
        "id": "itm_8p1zr5qw",
        "name": "AirPods Pro (USB-C)",
        "sku": "APP2-USBC-WHT",
        "price": 249.00,
        "quantity": 1
      }
    ],
    "shipping": {
      "method": "free",
      "address": {
        "line1": "One Apple Park Way",
        "city": "Cupertino",
        "state": "CA",
        "postcode": "95014",
        "country": "US"
      }
    },
    "totals": {
      "subtotal": 2248.00,
      "shipping": 0.00,
      "tax": 207.94,
      "total": 2455.94,
      "currency": "USD"
    }
  }
}`,
  `{
  "event": "payment.captured",
  "id": "evt_4r7n1qws",
  "timestamp": "2024-03-15T10:22:09Z",
  "data": {
    "payment_id": "pay_3m8xp2kn",
    "order_id": "ord_8f3k2x9p",
    "amount": 2455.94,
    "currency": "USD",
    "method": {
      "type": "apple_pay",
      "brand": "visa",
      "last4": "1776",
      "exp_month": 9,
      "exp_year": 2027
    },
    "billing": {
      "name": "Steve Jobs",
      "address": {
        "line1": "One Apple Park Way",
        "city": "Cupertino",
        "state": "CA",
        "postcode": "95014",
        "country": "US"
      }
    },
    "status": "succeeded",
    "risk_score": 2
  }
}`,
  `{
  "event": "inventory.updated",
  "id": "evt_7m3kp9xv",
  "timestamp": "2024-03-15T10:22:11Z",
  "data": {
    "updates": [
      {
        "sku": "MBP14-M4P-16-512-SLV",
        "product_id": "prod_tv3m8n",
        "warehouse": "wh_cali_01",
        "previous": 48,
        "current": 47,
        "reserved": 3
      },
      {
        "sku": "APP2-USBC-WHT",
        "product_id": "prod_kx9p2r",
        "warehouse": "wh_cali_01",
        "previous": 214,
        "current": 213,
        "reserved": 8
      }
    ],
    "trigger": "order.fulfilled",
    "order_id": "ord_8f3k2x9p"
  }
}`,
  `{
  "event": "shipment.dispatched",
  "id": "evt_2p5v8ztq",
  "timestamp": "2024-03-15T14:05:33Z",
  "data": {
    "shipment_id": "shp_7k1r4ynx",
    "order_id": "ord_8f3k2x9p",
    "carrier": {
      "name": "FedEx",
      "service": "Priority Overnight",
      "tracking_url": "https://www.fedex.com/tracking"
    },
    "tracking_number": "7489129045289",
    "items": [
      { "sku": "MBP14-M4P-16-512-SLV", "quantity": 1 },
      { "sku": "APP2-USBC-WHT", "quantity": 1 }
    ],
    "weight_kg": 1.84,
    "estimated_delivery": "2024-03-16",
    "label_created_at": "2024-03-15T13:58:12Z"
  }
}`,
  `{
  "event": "email.sent",
  "id": "evt_1q6hn7cz",
  "timestamp": "2024-03-15T14:06:01Z",
  "data": {
    "message_id": "msg_5r2wp4lv",
    "order_id": "ord_8f3k2x9p",
    "recipient": "s.jobs@icloud.com",
    "template": "shipment_confirmation",
    "subject": "Your order is on its way",
    "tracking_number": "7489129045289",
    "variables": {
      "customer_name": "Steve",
      "carrier": "FedEx",
      "estimated_delivery": "March 16, 2024"
    },
    "provider": "sendgrid",
    "status": "delivered"
  }
}`,
  `{
  "event": "review.submitted",
  "id": "evt_5n8wq3ym",
  "timestamp": "2024-03-17T09:14:52Z",
  "data": {
    "review_id": "rev_6z4qf8rp",
    "order_id": "ord_8f3k2x9p",
    "product_id": "prod_tv3m8n",
    "customer_id": "cus_4m9xp1r",
    "rating": 5,
    "title": "One more thing — it is perfect",
    "body": "Insanely great. The M4 Pro chip is a work of art.",
    "verified_purchase": true,
    "helpful_votes": 0,
    "moderation_status": "approved"
  }
}`,
]
```

### `registry/new-york/blocks/event-stream/styles/event-stream.css`

```css
/* event-stream — edge fade mask + cursor blink */

.es-root {
  -webkit-mask-image: linear-gradient(
    to bottom,
    transparent 0%,
    black 3%,
    black 97%,
    transparent 100%
  );
  mask-image: linear-gradient(
    to bottom,
    transparent 0%,
    black 3%,
    black 97%,
    transparent 100%
  );
}

@keyframes es-blink {
  0%,
  49.9% {
    opacity: 1;
  }
  50%,
  100% {
    opacity: 0;
  }
}

.es-cursor {
  animation: es-blink 1.15s step-start infinite;
}

@media (prefers-reduced-motion: reduce) {
  .es-cursor {
    animation: none;
  }
}
```
