Skip to content
edmi-ui

Ticker Strip

Horizontal strip of price cells: avatar and symbol, mono price, up/down change.

StackBlitz ↗StackBlitz ↗StackBlitz ↗
AAAPLx
$339.86
+0.42%
NNVDAx
$227.06
+0.81%
TTSLAx
$370.21
−0.31%
MMSFTx
$513.15
+0.12%
SSPYx
$767.86
+0.30%
AAAPLx
$339.86
+0.42%
NNVDAx
$227.06
+0.81%
TTSLAx
$370.21
−0.31%
MMSFTx
$513.15
+0.12%
SSPYx
$767.86
+0.30%
A
AAPLx
$339.86
+0.42%
N
NVDAx
$227.06
+0.81%
T
TSLAx
$370.21
−0.31%
M
MSFTx
$513.15
+0.12%
S
SPYx
$767.86
+0.30%
Terminal window
npx shadcn@latest add @edmi-ui/ticker-strip
Terminal window
pnpm dlx shadcn@latest add @edmi-ui/ticker-strip
Terminal window
yarn dlx shadcn@latest add @edmi-ui/ticker-strip
Terminal window
bunx shadcn@latest add @edmi-ui/ticker-strip

Pick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.

Terminal window
npx shadcn-vue@latest add @edmi-ui/ticker-strip
Terminal window
pnpm dlx shadcn-vue@latest add @edmi-ui/ticker-strip
Terminal window
yarn dlx shadcn-vue@latest add @edmi-ui/ticker-strip
Terminal window
bunx shadcn-vue@latest add @edmi-ui/ticker-strip

Pick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.

Terminal window
npx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/ticker-strip.json
Terminal window
pnpm dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/ticker-strip.json
Terminal window
yarn dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/ticker-strip.json
Terminal window
bunx --bun shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/ticker-strip.json

Pick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.

import { TickerStrip } from "@/components/ticker-strip/ticker-strip";
<TickerStrip
items={[
{ symbol: "AAPLx", price: "$339.86", change: "+0.42%" },
{ symbol: "NVDAx", price: "$227.06", change: "+0.81%" },
{ symbol: "TSLAx", price: "$370.21", change: "−0.31%" },
{ symbol: "MSFTx", price: "$513.15", change: "+0.12%" },
{ symbol: "SPYx", price: "$767.86", change: "+0.30%" },
]}
/>
<script setup lang="ts">
import { TickerStrip } from "@/components/ui/ticker-strip";
</script>
<template>
<TickerStrip />
</template>
<script lang="ts">
import { TickerStrip } from "$lib/components/ui/ticker-strip";
</script>
<TickerStrip />
registry/blocks/ticker-strip/ticker-strip.tsx
import { cn } from "cn";
import type * as React from "react";
import { Avatar, AvatarFallback, AvatarImage } from "@/registry/edmi/ui/avatar";
import { Card } from "@/registry/edmi/ui/card";
import type { Elevation } from "@/registry/edmi/ui/elevation";
type TickerItem = {
symbol: string;
price: string;
/** Signed percentage string, e.g. `+0.42%` or `−0.31%`. Sign sets the color. */
change: string;
/** Avatar image; falls back to the symbol's first letter. */
image?: string;
href?: string;
};
const isDown = (s: string) => /^[-−–]/.test(s.trim());
function TickerCell({ item }: { item: TickerItem }) {
const content = (
<>
<div className="flex items-center gap-2">
<Avatar className="size-[22px]">
{item.image ? <AvatarImage src={item.image} alt="" /> : null}
<AvatarFallback className="text-[9px]">
{item.symbol.charAt(0)}
</AvatarFallback>
</Avatar>
<span className="font-mono text-xs text-muted-foreground">
{item.symbol}
</span>
</div>
<div className="mt-2.5 font-mono text-[17px]">{item.price}</div>
<div
className={cn(
"mt-1 font-mono text-xs",
isDown(item.change) ? "text-destructive-text" : "text-success-text",
)}
>
{item.change}
</div>
</>
);
const cls =
"block min-w-[150px] flex-1 px-[18px] py-3.5 not-first:border-l not-first:border-border";
return item.href ? (
<a
href={item.href}
className={cn(
cls,
"hover:bg-[color-mix(in_srgb,var(--accent)_50%,var(--background))]",
)}
>
{content}
</a>
) : (
<div className={cls}>{content}</div>
);
}
// Horizontal row of price cells (avatar + symbol, mono price, up/down change).
function TickerStrip({
className,
items,
elevation,
...props
}: Omit<React.ComponentProps<typeof Card>, "children"> & {
items: TickerItem[];
/** ✦ depth of the strip (forwarded to Card). */
elevation?: Elevation;
}) {
return (
<Card
data-slot="ticker-strip"
elevation={elevation}
className={cn("flex-row gap-0 overflow-x-auto p-0", className)}
{...props}
>
{items.map((item) => (
<TickerCell key={item.symbol} item={item} />
))}
</Card>
);
}
export type { TickerItem };
export { TickerStrip };
registry/ui/ticker-strip/TickerStrip.vue
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import type { TickerItem } from '.'
import { cn } from '@/registry/edmi/lib/utils'
import { Avatar, AvatarFallback, AvatarImage } from '@/registry/edmi/ui/avatar'
import { Card } from '@/registry/edmi/ui/card'
import type { Elevation } from '@/registry/edmi/ui/elevation'
const props = defineProps<{
/** ✦ depth of the card (sunken -1, flat 0, raised +1, floating +2) */
elevation?: Elevation
items: TickerItem[]
class?: HTMLAttributes['class']
}>()
const isDown = (s: string) => /^[-−–]/.test(s.trim())
const cell = 'block min-w-[150px] flex-1 px-[18px] py-3.5 not-first:border-l not-first:border-border'
</script>
<template>
<!-- Horizontal row of price cells (avatar + symbol, mono price, up/down change). -->
<Card :elevation="elevation" data-slot="ticker-strip" :class="cn('flex-row gap-0 overflow-x-auto p-0', props.class)">
<component
:is="item.href ? 'a' : 'div'"
v-for="item in items"
:key="item.symbol"
:href="item.href"
:class="item.href ? cn(cell, 'hover:bg-[color-mix(in_srgb,var(--accent)_50%,var(--background))]') : cell"
>
<div class="flex items-center gap-2">
<Avatar class="size-[22px]">
<AvatarImage v-if="item.image" :src="item.image" alt="" />
<AvatarFallback class="text-[9px]">
{{ item.symbol.charAt(0) }}
</AvatarFallback>
</Avatar>
<span class="font-mono text-xs text-muted-foreground">{{ item.symbol }}</span>
</div>
<div class="mt-2.5 font-mono text-[17px]">
{{ item.price }}
</div>
<div :class="cn('mt-1 font-mono text-xs', isDown(item.change) ? 'text-destructive-text' : 'text-success-text')">
{{ item.change }}
</div>
</component>
</Card>
</template>
registry/ui/ticker-strip/index.ts
export type TickerItem = {
symbol: string;
price: string;
/** Signed percentage string, e.g. `+0.42%` or `−0.31%`. Sign sets the color. */
change: string;
/** Avatar image; falls back to the symbol's first letter. */
image?: string;
href?: string;
};
export { default as TickerStrip } from "./TickerStrip.vue";
src/lib/registry/ui/ticker-strip/index.ts
export { default as TickerStrip, type TickerItem } from "./ticker-strip.svelte";
src/lib/registry/ui/ticker-strip/ticker-strip.svelte
<script lang="ts" module>
export type TickerItem = {
symbol: string;
price: string;
/** Signed percentage string, e.g. `+0.42%` or `−0.31%`. Sign sets the color. */
change: string;
/** Avatar image; falls back to the symbol's first letter. */
image?: string;
href?: string;
};
</script>
<script lang="ts">
import { Avatar, AvatarFallback, AvatarImage } from "$lib/registry/ui/avatar/index.js";
import { Card } from "$lib/registry/ui/card/index.js";
import { cn } from "$lib/utils.js";
import type { Elevation } from "$lib/registry/ui/elevation/index.js";
import type { ComponentProps } from "svelte";
let {
ref = $bindable(null),
class: className,
items,
elevation = "auto",
...restProps
}: Omit<ComponentProps<typeof Card>, "children" | "size"> & {
items: TickerItem[];
/** ✦ depth of the strip (forwarded to Card). */
elevation?: Elevation;
} = $props();
const isDown = (s: string) => /^[-−–]/.test(s.trim());
const cell = "block min-w-[150px] flex-1 px-[18px] py-3.5 not-first:border-l not-first:border-border";
</script>
{#snippet content(item: TickerItem)}
<div class="flex items-center gap-2">
<Avatar class="size-[22px]">
{#if item.image}<AvatarImage src={item.image} alt="" />{/if}
<AvatarFallback class="text-[9px]">{item.symbol.charAt(0)}</AvatarFallback>
</Avatar>
<span class="font-mono text-xs text-muted-foreground">{item.symbol}</span>
</div>
<div class="mt-2.5 font-mono text-[17px]">{item.price}</div>
<div class={cn("mt-1 font-mono text-xs", isDown(item.change) ? "text-destructive-text" : "text-success-text")}>
{item.change}
</div>
{/snippet}
<!-- Horizontal row of price cells (avatar + symbol, mono price, up/down change). -->
<Card bind:ref data-slot="ticker-strip" {elevation} class={cn("flex-row gap-0 overflow-x-auto p-0", className)} {...restProps}>
{#each items as item (item.symbol)}
{#if item.href}
<a href={item.href} class={cn(cell, "hover:bg-[color-mix(in_srgb,var(--accent)_50%,var(--background))]")}>{@render content(item)}</a>
{:else}
<div class={cell}>{@render content(item)}</div>
{/if}
{/each}
</Card>

See the Elevation guide for the levels, roles and layered mode.

Flat by default. elevation is forwarded to the card: sunken (-1) a soft inset well, raised (+1) the bevel, floating (+2) the bevel plus one soft drop. In a layered ElevationProvider the card resolves to its role default (raised).

StackBlitz ↗StackBlitz ↗StackBlitz ↗

Flat (0)

AAAPLx
$339.86
+0.42%
NNVDAx
$227.06
+0.81%
TTSLAx
$370.21
−0.31%
MMSFTx
$513.15
+0.12%
SSPYx
$767.86
+0.30%

Raised (+1)

AAAPLx
$339.86
+0.42%
NNVDAx
$227.06
+0.81%
TTSLAx
$370.21
−0.31%
MMSFTx
$513.15
+0.12%
SSPYx
$767.86
+0.30%

Floating (+2)

AAAPLx
$339.86
+0.42%
NNVDAx
$227.06
+0.81%
TTSLAx
$370.21
−0.31%
MMSFTx
$513.15
+0.12%
SSPYx
$767.86
+0.30%

Flat (0)

AAAPLx
$339.86
+0.42%
NNVDAx
$227.06
+0.81%
TTSLAx
$370.21
−0.31%
MMSFTx
$513.15
+0.12%
SSPYx
$767.86
+0.30%

Raised (+1)

AAAPLx
$339.86
+0.42%
NNVDAx
$227.06
+0.81%
TTSLAx
$370.21
−0.31%
MMSFTx
$513.15
+0.12%
SSPYx
$767.86
+0.30%

Floating (+2)

AAAPLx
$339.86
+0.42%
NNVDAx
$227.06
+0.81%
TTSLAx
$370.21
−0.31%
MMSFTx
$513.15
+0.12%
SSPYx
$767.86
+0.30%

Flat (0)

A
AAPLx
$339.86
+0.42%
N
NVDAx
$227.06
+0.81%
T
TSLAx
$370.21
−0.31%
M
MSFTx
$513.15
+0.12%
S
SPYx
$767.86
+0.30%

Raised (+1)

A
AAPLx
$339.86
+0.42%
N
NVDAx
$227.06
+0.81%
T
TSLAx
$370.21
−0.31%
M
MSFTx
$513.15
+0.12%
S
SPYx
$767.86
+0.30%

Floating (+2)

A
AAPLx
$339.86
+0.42%
N
NVDAx
$227.06
+0.81%
T
TSLAx
$370.21
−0.31%
M
MSFTx
$513.15
+0.12%
S
SPYx
$767.86
+0.30%
Part Prop Type Default
TickerStrip items { symbol, price, change, image?, href? }[] required
elevation "auto" | "sunken" | "flat" | "raised" | "floating" "auto"

change is a signed string; a leading -/− renders --destructive-text, otherwise --success-text.

  • Scrolls horizontally when cells do not fit.