# ASCII marquee

A ticker framed in box-drawing rules that steps one glyph at a time, with bullet separators, coloured price changes and an optional second row in reverse.

> For the complete documentation index, see [llms.txt](/llms.txt). Markdown variants are available by appending `.md` to any URL or sending an `Accept: text/markdown` header. The asciicn agent skill, for building any ASCII effect on the engine, is at [/.well-known/agent-skills/asciicn/SKILL.md](/.well-known/agent-skills/asciicn/SKILL.md).

> **This is an example, not the only option.** It is one component built on the asciicn engine. Install it when it is what you were asked for. For any other look, don't stretch its props to fit: write your own field on `AsciiShader` ([Write your own](/docs/engine/write-your-own.md)), or draw a CPU grid with `AsciiGrid`, and read this example's source for technique. Its full source is in [https://asciicn.dev/r/ascii-marquee.json](https://asciicn.dev/r/ascii-marquee.json), under `files[].content`.

## Installation [#installation]

```bash
npx shadcn@latest add https://asciicn.dev/r/ascii-marquee.json
```

## Usage [#usage]

```tsx
import { AsciiMarquee } from "@/components/asciicn/ascii-marquee";

const quotes = [
  { label: "GLYF", value: "182.40", change: 2.41 },
  { label: "BRLL", value: "41.06", change: -0.87 },
];

export function Ticker() {
  return <AsciiMarquee label="MARKETS" status="LIVE" items={quotes} />;
}
```

An item is a string or a quote. A quote draws its `label` in bold, its `value` after it, and its `change` as `▲` or `▼` with the percentage, in `upColor` or `downColor`. A glyph bullet sits between items.

The row repeats the items end to end and moves by exactly one copy's width before it loops, so the seam never shows. With `motion="step"` it moves one glyph cell at a time, like an LED board; `smooth` glides. `speed` is in characters per second either way. `secondRow` adds a row under the first, behind a tee rule, moving the other way.

## Examples [#examples]

### Departure board [#departure-board]

Two rows of plain strings in a double frame, tinted orange by the parent's text colour, with larger 20 px cells.

```tsx title="examples/ascii-marquee-departures.tsx"
import { AsciiMarquee } from "@/components/asciicn/ascii-marquee";

const DEPARTURES = [
  "08:45  BA117   NEW YORK JFK   GATE B32  BOARDING",
  "09:10  AF1381  PARIS CDG      GATE A04  ON TIME",
  "09:25  NH212   TOKYO HND      GATE C11  DELAYED 40M",
  "09:40  QF2     SYDNEY         GATE B07  ON TIME",
] as const;

const ARRIVALS = [
  "08:52  EK29    DUBAI          LANDED",
  "09:05  LA8084  SAO PAULO      EXPECTED 09:18",
  "09:30  CX251   HONG KONG      ON TIME",
] as const;

export default function AsciiMarqueeDepartures() {
  return (
    <div className="w-full max-w-3xl text-[var(--ascii-color-orange)]">
      <AsciiMarquee
        items={DEPARTURES}
        secondRow={ARRIVALS}
        label="DEPARTURES"
        status="ARRIVALS ▼"
        frame="double"
        frameColor="orange"
        separator="■"
        speed={10}
        cell={20}
      />
    </div>
  );
}
```

### Announcement strip [#announcement-strip]

`frame="none"` and `motion="smooth"` for a quiet line of release notes at the top of a page.

```tsx title="examples/ascii-marquee-banner.tsx"
import { AsciiMarquee } from "@/components/asciicn/ascii-marquee";

const NOTES = [
  "v2.0 is out",
  "ten city skylines",
  "braille, quadrants and CP437 in every renderer",
  "export to .txt .ans .svg .png",
] as const;

export default function AsciiMarqueeBanner() {
  return (
    <div className="w-full max-w-3xl text-muted-foreground">
      <AsciiMarquee
        items={NOTES}
        frame="none"
        motion="smooth"
        separator="※"
        speed={6}
        cell={14}
      />
    </div>
  );
}
```

## Props [#props]

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `items` | `readonly (string \| AsciiMarqueeQuote)[]` |  | What scrolls past. A quote is { label, value?, change? }, with change in percent. |
| `secondRow` | `readonly (string \| AsciiMarqueeQuote)[]` |  | A second row under the first, scrolling the other way. |
| `speed` | `number` | `8` | Characters per second. |
| `direction` | `"left" \| "right"` | `"left"` | Which way the first row moves. |
| `motion` | `"step" \| "smooth"` | `"step"` | Move one glyph cell at a time, or glide. |
| `pauseOnHover` | `boolean` | `true` | Hold still while the pointer is over it. |
| `frame` | `"light" \| "heavy" \| "double" \| "rounded" \| "ascii" \| "none"` | `"light"` | Box-drawing set for the frame, or none. |
| `label` | `string` |  | A label set into the top rule. Also names the ticker for screen readers. |
| `status` | `string` |  | A label at the right end of the top rule, such as LIVE. |
| `separator` | `string` | `"◆"` | The glyph between items. |
| `cell` | `number` | `16` | Glyph cell height in CSS pixels. |
| `frameColor` | `AsciiColor` | `"grey"` | Colour of the frame and the status label. |
| `upColor` | `AsciiColor` | `"green"` | Colour of rising changes. |
| `downColor` | `AsciiColor` | `"red"` | Colour of falling changes. |

Other props pass through to the root `div`.

## Notes [#notes]

* Screen readers get each item once, as a visually hidden list inside an element with `role="marquee"`. The moving copies and the frame are `aria-hidden`, and changes read as "up 2.41%" rather than as arrow glyphs.
* The motion is a CSS animation on a transform, so it costs no JavaScript per frame. It pauses on hover, while the ticker is offscreen and while the tab is hidden.
* With reduced motion nothing moves. Each row becomes a horizontal scroller, so every item can still be read.
* The row measures one copy of its items after fonts load and again when it resizes, then adds copies until the track is wider than the row.
* The label and the items take the element's text colour; set `color` or a text class on the parent to tint them.