Ticker Strip
Horizontal strip of price cells: avatar and symbol, mono price, up/down change.
import { TickerStrip } from "@/components/ticker-strip/ticker-strip";
export default function Demo() { return ( <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";
const 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>
<template> <TickerStrip :items="items" /></template><script lang="ts"> import { TickerStrip } from "$lib/components/ui/ticker-strip";
const 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>
<TickerStrip {items} />Installation
Section titled “Installation”npx shadcn@latest add @edmi-ui/ticker-strippnpm dlx shadcn@latest add @edmi-ui/ticker-stripyarn dlx shadcn@latest add @edmi-ui/ticker-stripbunx shadcn@latest add @edmi-ui/ticker-stripPick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.
Add the Edmi components this one builds on:
card,avatar,elevation.Copy the code from Component source into your project and update the import paths to match your aliases.
npx shadcn-vue@latest add @edmi-ui/ticker-strippnpm dlx shadcn-vue@latest add @edmi-ui/ticker-stripyarn dlx shadcn-vue@latest add @edmi-ui/ticker-stripbunx shadcn-vue@latest add @edmi-ui/ticker-stripPick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.
Add the Edmi components this one builds on:
card,avatar,elevation.Copy the code from Component source into your project and update the import paths to match your aliases.
npx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/ticker-strip.jsonpnpm dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/ticker-strip.jsonyarn dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/ticker-strip.jsonbunx --bun shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/ticker-strip.jsonPick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.
Add the Edmi components this one builds on:
card,avatar,elevation.Copy the code from Component source into your project and update the import paths to match your aliases.
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 />Component source
Section titled “Component source”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 };<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>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";export { default as TickerStrip, type TickerItem } from "./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>Elevation ✦
Section titled “Elevation ✦”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).
Flat (0)
Raised (+1)
Floating (+2)
Flat (0)
Raised (+1)
Floating (+2)
Flat (0)
Raised (+1)
Floating (+2)
import { TickerStrip } from "@/components/ticker-strip/ticker-strip";import type { Elevation } from "@/components/ui/elevation";
function Sample({ elevation }: { elevation: Elevation }) { return ( <TickerStrip elevation={elevation} 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%" }, ]} /> );}
const levels = [ { value: "flat", label: "Flat (0)" }, { value: "raised", label: "Raised (+1)" }, { value: "floating", label: "Floating (+2)" },] as const;
export default function Demo() { return ( <div className="flex flex-col gap-6"> {levels.map(({ value, label }) => ( <div key={value} className="flex flex-col gap-2"> <p className="text-xs font-medium text-muted-foreground">{label}</p> <Sample elevation={value} /> </div> ))} </div> );}<script setup lang="ts">import { TickerStrip } from "@/components/ui/ticker-strip";
const 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%" },];
const levels = [ { value: "flat", label: "Flat (0)" } as const, { value: "raised", label: "Raised (+1)" } as const, { value: "floating", label: "Floating (+2)" } as const,];</script>
<template> <div class="flex flex-col gap-6"> <div v-for="l in levels" :key="l.value" class="flex flex-col gap-2"> <p class="text-xs font-medium text-muted-foreground">{{ l.label }}</p> <TickerStrip :elevation="l.value" :items="items" /> </div> </div></template><script lang="ts"> import { TickerStrip } from "$lib/components/ui/ticker-strip";
const 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%" }, ];
const levels = [ { value: "flat", label: "Flat (0)" } as const, { value: "raised", label: "Raised (+1)" } as const, { value: "floating", label: "Floating (+2)" } as const, ];</script>
<div class="flex flex-col gap-6"> {#each levels as l (l.value)} <div class="flex flex-col gap-2"> <p class="text-xs font-medium text-muted-foreground">{l.label}</p><TickerStrip elevation={l.value} {items} /> </div> {/each}</div>| 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.
✦ Edmi additions
Section titled “✦ Edmi additions”- Scrolls horizontally when cells do not fit.