Finance
Ticker
Composable financial ticker components for React and Next.js applications. Built with TypeScript support, Tailwind CSS styling, and shadcn/ui design for professional stock prices, currency rates, and market data visualization.
Loading component...
'use client';import { Ticker, TickerIcon, TickerPrice, TickerPriceChange, TickerSymbol,} from '@/components/ui/shadcn-io/ticker';const Example = () => ( <Ticker> <TickerIcon src="https://raw.githubusercontent.com/nvstly/icons/refs/heads/main/ticker_icons/GOOG.png" symbol="GOOG" /> <TickerSymbol symbol="GOOG" /> <TickerPrice price={175.41} /> <TickerPriceChange change={2.13} /> </Ticker>);export default Example;
'use client';import type { HTMLAttributes, ReactNode } from 'react';import { createContext, memo, useContext, useMemo } from 'react';import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';import { cn } from '@/lib/utils';type TickerContextValue = { formatter: Intl.NumberFormat;};const DEFAULT_CURRENCY = 'USD';const DEFAULT_LOCALE = 'en-US';const defaultFormatter = new Intl.NumberFormat(DEFAULT_LOCALE, { style: 'currency', currency: DEFAULT_CURRENCY, minimumFractionDigits: 2, maximumFractionDigits: 2,});const TickerContext = createContext<TickerContextValue>({ formatter: defaultFormatter,});export const useTickerContext = () => useContext(TickerContext);export type TickerProps = HTMLAttributes<HTMLButtonElement> & { currency?: string; locale?: string;};export const Ticker = memo( ({ children, className, currency = DEFAULT_CURRENCY, locale = DEFAULT_LOCALE, ...props }: TickerProps & { children: ReactNode }) => { const formatter = useMemo(() => { try { return new Intl.NumberFormat(locale, { style: 'currency', currency: currency.toUpperCase(), minimumFractionDigits: 2, maximumFractionDigits: 2, }); } catch { return defaultFormatter; } }, [currency, locale]); return ( <TickerContext.Provider value={{ formatter }}> <button className={cn( 'inline-flex items-center gap-1.5 whitespace-nowrap align-middle', className )} type="button" {...props} > {children} </button> </TickerContext.Provider> ); });Ticker.displayName = 'Ticker';export type TickerIconProps = HTMLAttributes<HTMLImageElement> & { src: string; symbol: string;};export const TickerIcon = memo( ({ src, symbol, className, ...props }: TickerIconProps) => { if (!src) { return null; } return ( <Avatar className={cn('size-7 border border-border bg-muted p-1', className)} > <AvatarImage src={src} {...props} /> <AvatarFallback className="font-semibold text-muted-foreground text-sm"> {symbol.slice(0, 2).toUpperCase()} </AvatarFallback> </Avatar> ); });TickerIcon.displayName = 'TickerIcon';export type TickerSymbolProps = HTMLAttributes<HTMLSpanElement> & { symbol: string;};export const TickerSymbol = memo( ({ symbol, className, ...props }: TickerSymbolProps) => ( <span className={cn('font-medium', className)} {...props}> {symbol.toUpperCase()} </span> ));TickerSymbol.displayName = 'TickerSymbol';export type TickerPriceProps = HTMLAttributes<HTMLSpanElement> & { price: number;};export const TickerPrice = memo( ({ price, className, ...props }: TickerPriceProps) => { const context = useTickerContext(); const formattedPrice = useMemo( () => context.formatter.format(price), [price, context] ); return ( <span className={cn('text-muted-foreground', className)} {...props}> {formattedPrice} </span> ); });TickerPrice.displayName = 'TickerPrice';export type TickerPriceChangeProps = HTMLAttributes<HTMLSpanElement> & { change: number; isPercent?: boolean;};export const TickerPriceChange = memo( ({ change, isPercent, className, ...props }: TickerPriceChangeProps) => { const isPositiveChange = useMemo(() => change >= 0, [change]); const context = useTickerContext(); const changeFormatted = useMemo(() => { if (isPercent) { return `${change.toFixed(2)}%`; } return context.formatter.format(change); }, [change, isPercent, context]); return ( <span className={cn( 'flex items-center gap-0.5', isPositiveChange ? 'text-green-600 dark:text-green-500' : 'text-red-600 dark:text-red-500', className )} {...props} > <svg aria-labelledby="ticker-change-icon-title" className={isPositiveChange ? '' : 'rotate-180'} fill="currentColor" height="12" role="img" viewBox="0 0 24 24" width="12" xmlns="http://www.w3.org/2000/svg" > <title id="ticker-change-icon-title"> {isPositiveChange ? 'Up icon' : 'Down icon'} </title> <path d="M24 22h-24l12-20z" /> </svg> {changeFormatted} </span> ); });TickerPriceChange.displayName = 'TickerPriceChange';
Installation
npx shadcn@latest add https://www.shadcn.io/registry/ticker.json
npx shadcn@latest add https://www.shadcn.io/registry/ticker.json
pnpm dlx shadcn@latest add https://www.shadcn.io/registry/ticker.json
bunx shadcn@latest add https://www.shadcn.io/registry/ticker.json
Features
- Flexible composition offering optional icon, symbol, price, and change components for React financial interfaces
- Color-coded indicators displaying up/down price changes with percentage formatting using Tailwind CSS utilities
- International support featuring ISO 4217 currencies and IETF BCP 47 locales for Next.js applications
- Smart fallbacks providing automatic icon-to-symbol fallback when sources are invalid using TypeScript safety
- Responsive layouts adapting ticker displays across device sizes with mobile-friendly design using shadcn/ui components
- Real-time updates supporting dynamic price changes and market data refresh with JavaScript state management
- Accessibility compliant including screen reader support, keyboard navigation, and semantic markup
- Open source freedom providing unlimited use for financial applications without licensing restrictions
Examples
With percentage change
Loading component...
'use client';import { Ticker, TickerIcon, type TickerIconProps, TickerPrice, TickerPriceChange, type TickerPriceChangeProps, type TickerPriceProps, TickerSymbol, type TickerSymbolProps,} from '@/components/ui/shadcn-io/ticker';const items: { symbol: TickerSymbolProps['symbol']; src: TickerIconProps['src']; price: TickerPriceProps['price']; change: TickerPriceChangeProps['change'];}[] = [ { symbol: 'TSLA', src: 'https://raw.githubusercontent.com/nvstly/icons/refs/heads/main/ticker_icons/TSLA.png', price: 182.12, change: -3.12, }, { symbol: 'MSFT', src: 'https://raw.githubusercontent.com/nvstly/icons/refs/heads/main/ticker_icons/MSFT.png', price: 409.33, change: 2.18, },];const Example = () => ( <> {items.map((i) => ( <Ticker key={i.symbol}> <TickerIcon src={i.src} symbol={i.symbol} /> <TickerSymbol symbol={i.symbol} /> <TickerPrice price={i.price} /> <TickerPriceChange change={i.change} isPercent /> </Ticker> ))} </>);export default Example;
'use client';import type { HTMLAttributes, ReactNode } from 'react';import { createContext, memo, useContext, useMemo } from 'react';import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';import { cn } from '@/lib/utils';type TickerContextValue = { formatter: Intl.NumberFormat;};const DEFAULT_CURRENCY = 'USD';const DEFAULT_LOCALE = 'en-US';const defaultFormatter = new Intl.NumberFormat(DEFAULT_LOCALE, { style: 'currency', currency: DEFAULT_CURRENCY, minimumFractionDigits: 2, maximumFractionDigits: 2,});const TickerContext = createContext<TickerContextValue>({ formatter: defaultFormatter,});export const useTickerContext = () => useContext(TickerContext);export type TickerProps = HTMLAttributes<HTMLButtonElement> & { currency?: string; locale?: string;};export const Ticker = memo( ({ children, className, currency = DEFAULT_CURRENCY, locale = DEFAULT_LOCALE, ...props }: TickerProps & { children: ReactNode }) => { const formatter = useMemo(() => { try { return new Intl.NumberFormat(locale, { style: 'currency', currency: currency.toUpperCase(), minimumFractionDigits: 2, maximumFractionDigits: 2, }); } catch { return defaultFormatter; } }, [currency, locale]); return ( <TickerContext.Provider value={{ formatter }}> <button className={cn( 'inline-flex items-center gap-1.5 whitespace-nowrap align-middle', className )} type="button" {...props} > {children} </button> </TickerContext.Provider> ); });Ticker.displayName = 'Ticker';export type TickerIconProps = HTMLAttributes<HTMLImageElement> & { src: string; symbol: string;};export const TickerIcon = memo( ({ src, symbol, className, ...props }: TickerIconProps) => { if (!src) { return null; } return ( <Avatar className={cn('size-7 border border-border bg-muted p-1', className)} > <AvatarImage src={src} {...props} /> <AvatarFallback className="font-semibold text-muted-foreground text-sm"> {symbol.slice(0, 2).toUpperCase()} </AvatarFallback> </Avatar> ); });TickerIcon.displayName = 'TickerIcon';export type TickerSymbolProps = HTMLAttributes<HTMLSpanElement> & { symbol: string;};export const TickerSymbol = memo( ({ symbol, className, ...props }: TickerSymbolProps) => ( <span className={cn('font-medium', className)} {...props}> {symbol.toUpperCase()} </span> ));TickerSymbol.displayName = 'TickerSymbol';export type TickerPriceProps = HTMLAttributes<HTMLSpanElement> & { price: number;};export const TickerPrice = memo( ({ price, className, ...props }: TickerPriceProps) => { const context = useTickerContext(); const formattedPrice = useMemo( () => context.formatter.format(price), [price, context] ); return ( <span className={cn('text-muted-foreground', className)} {...props}> {formattedPrice} </span> ); });TickerPrice.displayName = 'TickerPrice';export type TickerPriceChangeProps = HTMLAttributes<HTMLSpanElement> & { change: number; isPercent?: boolean;};export const TickerPriceChange = memo( ({ change, isPercent, className, ...props }: TickerPriceChangeProps) => { const isPositiveChange = useMemo(() => change >= 0, [change]); const context = useTickerContext(); const changeFormatted = useMemo(() => { if (isPercent) { return `${change.toFixed(2)}%`; } return context.formatter.format(change); }, [change, isPercent, context]); return ( <span className={cn( 'flex items-center gap-0.5', isPositiveChange ? 'text-green-600 dark:text-green-500' : 'text-red-600 dark:text-red-500', className )} {...props} > <svg aria-labelledby="ticker-change-icon-title" className={isPositiveChange ? '' : 'rotate-180'} fill="currentColor" height="12" role="img" viewBox="0 0 24 24" width="12" xmlns="http://www.w3.org/2000/svg" > <title id="ticker-change-icon-title"> {isPositiveChange ? 'Up icon' : 'Down icon'} </title> <path d="M24 22h-24l12-20z" /> </svg> {changeFormatted} </span> ); });TickerPriceChange.displayName = 'TickerPriceChange';
Currencies and locales
Loading component...
'use client';import { Ticker, TickerIcon, type TickerIconProps, TickerPrice, TickerPriceChange, type TickerPriceChangeProps, type TickerPriceProps, type TickerProps, TickerSymbol, type TickerSymbolProps,} from '@/components/ui/shadcn-io/ticker';const items: { symbol: TickerSymbolProps['symbol']; src: TickerIconProps['src']; price: TickerPriceProps['price']; change: TickerPriceChangeProps['change']; currency?: TickerProps['currency']; locale?: TickerProps['locale'];}[] = [ { symbol: 'DUOL', src: 'https://raw.githubusercontent.com/nvstly/icons/refs/heads/main/ticker_icons/DUOL.png', price: 478.03, change: 5.2, }, { symbol: 'DBD', src: 'https://raw.githubusercontent.com/nvstly/icons/refs/heads/main/ticker_icons/DBD.png', price: 102.33, change: 1.05, currency: 'EUR', locale: 'de-DE', }, { symbol: '7203.T', src: 'https://raw.githubusercontent.com/nvstly/icons/refs/heads/main/ticker_icons/TM.png', price: 2460, change: -120, currency: 'JPY', locale: 'ja-JP', },];const Example = () => items.map((i) => ( <Ticker currency={i.currency} key={i.symbol} locale={i.locale}> <TickerIcon src={i.src} symbol={i.symbol} /> <TickerSymbol symbol={i.symbol} /> <TickerPrice price={i.price} /> <TickerPriceChange change={i.change} /> </Ticker> ));export default Example;
'use client';import type { HTMLAttributes, ReactNode } from 'react';import { createContext, memo, useContext, useMemo } from 'react';import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';import { cn } from '@/lib/utils';type TickerContextValue = { formatter: Intl.NumberFormat;};const DEFAULT_CURRENCY = 'USD';const DEFAULT_LOCALE = 'en-US';const defaultFormatter = new Intl.NumberFormat(DEFAULT_LOCALE, { style: 'currency', currency: DEFAULT_CURRENCY, minimumFractionDigits: 2, maximumFractionDigits: 2,});const TickerContext = createContext<TickerContextValue>({ formatter: defaultFormatter,});export const useTickerContext = () => useContext(TickerContext);export type TickerProps = HTMLAttributes<HTMLButtonElement> & { currency?: string; locale?: string;};export const Ticker = memo( ({ children, className, currency = DEFAULT_CURRENCY, locale = DEFAULT_LOCALE, ...props }: TickerProps & { children: ReactNode }) => { const formatter = useMemo(() => { try { return new Intl.NumberFormat(locale, { style: 'currency', currency: currency.toUpperCase(), minimumFractionDigits: 2, maximumFractionDigits: 2, }); } catch { return defaultFormatter; } }, [currency, locale]); return ( <TickerContext.Provider value={{ formatter }}> <button className={cn( 'inline-flex items-center gap-1.5 whitespace-nowrap align-middle', className )} type="button" {...props} > {children} </button> </TickerContext.Provider> ); });Ticker.displayName = 'Ticker';export type TickerIconProps = HTMLAttributes<HTMLImageElement> & { src: string; symbol: string;};export const TickerIcon = memo( ({ src, symbol, className, ...props }: TickerIconProps) => { if (!src) { return null; } return ( <Avatar className={cn('size-7 border border-border bg-muted p-1', className)} > <AvatarImage src={src} {...props} /> <AvatarFallback className="font-semibold text-muted-foreground text-sm"> {symbol.slice(0, 2).toUpperCase()} </AvatarFallback> </Avatar> ); });TickerIcon.displayName = 'TickerIcon';export type TickerSymbolProps = HTMLAttributes<HTMLSpanElement> & { symbol: string;};export const TickerSymbol = memo( ({ symbol, className, ...props }: TickerSymbolProps) => ( <span className={cn('font-medium', className)} {...props}> {symbol.toUpperCase()} </span> ));TickerSymbol.displayName = 'TickerSymbol';export type TickerPriceProps = HTMLAttributes<HTMLSpanElement> & { price: number;};export const TickerPrice = memo( ({ price, className, ...props }: TickerPriceProps) => { const context = useTickerContext(); const formattedPrice = useMemo( () => context.formatter.format(price), [price, context] ); return ( <span className={cn('text-muted-foreground', className)} {...props}> {formattedPrice} </span> ); });TickerPrice.displayName = 'TickerPrice';export type TickerPriceChangeProps = HTMLAttributes<HTMLSpanElement> & { change: number; isPercent?: boolean;};export const TickerPriceChange = memo( ({ change, isPercent, className, ...props }: TickerPriceChangeProps) => { const isPositiveChange = useMemo(() => change >= 0, [change]); const context = useTickerContext(); const changeFormatted = useMemo(() => { if (isPercent) { return `${change.toFixed(2)}%`; } return context.formatter.format(change); }, [change, isPercent, context]); return ( <span className={cn( 'flex items-center gap-0.5', isPositiveChange ? 'text-green-600 dark:text-green-500' : 'text-red-600 dark:text-red-500', className )} {...props} > <svg aria-labelledby="ticker-change-icon-title" className={isPositiveChange ? '' : 'rotate-180'} fill="currentColor" height="12" role="img" viewBox="0 0 24 24" width="12" xmlns="http://www.w3.org/2000/svg" > <title id="ticker-change-icon-title"> {isPositiveChange ? 'Up icon' : 'Down icon'} </title> <path d="M24 22h-24l12-20z" /> </svg> {changeFormatted} </span> ); });TickerPriceChange.displayName = 'TickerPriceChange';
Icon fallback to symbol
Loading component...
'use client';import { Ticker, TickerIcon, TickerPrice, TickerPriceChange, TickerSymbol,} from '@/components/ui/shadcn-io/ticker';const Example = () => ( <Ticker> <TickerIcon src="invalid-icon-url" symbol="AAPL" /> <TickerSymbol symbol="AAPL" /> <TickerPrice price={196.58} /> <TickerPriceChange change={-1.25} /> </Ticker>);export default Example;
'use client';import type { HTMLAttributes, ReactNode } from 'react';import { createContext, memo, useContext, useMemo } from 'react';import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';import { cn } from '@/lib/utils';type TickerContextValue = { formatter: Intl.NumberFormat;};const DEFAULT_CURRENCY = 'USD';const DEFAULT_LOCALE = 'en-US';const defaultFormatter = new Intl.NumberFormat(DEFAULT_LOCALE, { style: 'currency', currency: DEFAULT_CURRENCY, minimumFractionDigits: 2, maximumFractionDigits: 2,});const TickerContext = createContext<TickerContextValue>({ formatter: defaultFormatter,});export const useTickerContext = () => useContext(TickerContext);export type TickerProps = HTMLAttributes<HTMLButtonElement> & { currency?: string; locale?: string;};export const Ticker = memo( ({ children, className, currency = DEFAULT_CURRENCY, locale = DEFAULT_LOCALE, ...props }: TickerProps & { children: ReactNode }) => { const formatter = useMemo(() => { try { return new Intl.NumberFormat(locale, { style: 'currency', currency: currency.toUpperCase(), minimumFractionDigits: 2, maximumFractionDigits: 2, }); } catch { return defaultFormatter; } }, [currency, locale]); return ( <TickerContext.Provider value={{ formatter }}> <button className={cn( 'inline-flex items-center gap-1.5 whitespace-nowrap align-middle', className )} type="button" {...props} > {children} </button> </TickerContext.Provider> ); });Ticker.displayName = 'Ticker';export type TickerIconProps = HTMLAttributes<HTMLImageElement> & { src: string; symbol: string;};export const TickerIcon = memo( ({ src, symbol, className, ...props }: TickerIconProps) => { if (!src) { return null; } return ( <Avatar className={cn('size-7 border border-border bg-muted p-1', className)} > <AvatarImage src={src} {...props} /> <AvatarFallback className="font-semibold text-muted-foreground text-sm"> {symbol.slice(0, 2).toUpperCase()} </AvatarFallback> </Avatar> ); });TickerIcon.displayName = 'TickerIcon';export type TickerSymbolProps = HTMLAttributes<HTMLSpanElement> & { symbol: string;};export const TickerSymbol = memo( ({ symbol, className, ...props }: TickerSymbolProps) => ( <span className={cn('font-medium', className)} {...props}> {symbol.toUpperCase()} </span> ));TickerSymbol.displayName = 'TickerSymbol';export type TickerPriceProps = HTMLAttributes<HTMLSpanElement> & { price: number;};export const TickerPrice = memo( ({ price, className, ...props }: TickerPriceProps) => { const context = useTickerContext(); const formattedPrice = useMemo( () => context.formatter.format(price), [price, context] ); return ( <span className={cn('text-muted-foreground', className)} {...props}> {formattedPrice} </span> ); });TickerPrice.displayName = 'TickerPrice';export type TickerPriceChangeProps = HTMLAttributes<HTMLSpanElement> & { change: number; isPercent?: boolean;};export const TickerPriceChange = memo( ({ change, isPercent, className, ...props }: TickerPriceChangeProps) => { const isPositiveChange = useMemo(() => change >= 0, [change]); const context = useTickerContext(); const changeFormatted = useMemo(() => { if (isPercent) { return `${change.toFixed(2)}%`; } return context.formatter.format(change); }, [change, isPercent, context]); return ( <span className={cn( 'flex items-center gap-0.5', isPositiveChange ? 'text-green-600 dark:text-green-500' : 'text-red-600 dark:text-red-500', className )} {...props} > <svg aria-labelledby="ticker-change-icon-title" className={isPositiveChange ? '' : 'rotate-180'} fill="currentColor" height="12" role="img" viewBox="0 0 24 24" width="12" xmlns="http://www.w3.org/2000/svg" > <title id="ticker-change-icon-title"> {isPositiveChange ? 'Up icon' : 'Down icon'} </title> <path d="M24 22h-24l12-20z" /> </svg> {changeFormatted} </span> ); });TickerPriceChange.displayName = 'TickerPriceChange';
Inline usage
Loading component...
'use client';import { Ticker, TickerIcon, TickerPrice, TickerPriceChange, TickerSymbol,} from '@/components/ui/shadcn-io/ticker';const Example = () => ( <p> In other autonomous vehicle news, Alphabet-owned{' '} <Ticker className="px-1 text-base"> <TickerIcon src="https://raw.githubusercontent.com/nvstly/icons/refs/heads/main/ticker_icons/GOOG.png" symbol="GOOG" /> <TickerSymbol symbol="GOOG" /> <TickerPrice price={175.41} /> <TickerPriceChange change={2.13} /> </Ticker>{' '} Waymo is looking to bring its robotaxi service to New York. </p>);export default Example;
'use client';import type { HTMLAttributes, ReactNode } from 'react';import { createContext, memo, useContext, useMemo } from 'react';import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';import { cn } from '@/lib/utils';type TickerContextValue = { formatter: Intl.NumberFormat;};const DEFAULT_CURRENCY = 'USD';const DEFAULT_LOCALE = 'en-US';const defaultFormatter = new Intl.NumberFormat(DEFAULT_LOCALE, { style: 'currency', currency: DEFAULT_CURRENCY, minimumFractionDigits: 2, maximumFractionDigits: 2,});const TickerContext = createContext<TickerContextValue>({ formatter: defaultFormatter,});export const useTickerContext = () => useContext(TickerContext);export type TickerProps = HTMLAttributes<HTMLButtonElement> & { currency?: string; locale?: string;};export const Ticker = memo( ({ children, className, currency = DEFAULT_CURRENCY, locale = DEFAULT_LOCALE, ...props }: TickerProps & { children: ReactNode }) => { const formatter = useMemo(() => { try { return new Intl.NumberFormat(locale, { style: 'currency', currency: currency.toUpperCase(), minimumFractionDigits: 2, maximumFractionDigits: 2, }); } catch { return defaultFormatter; } }, [currency, locale]); return ( <TickerContext.Provider value={{ formatter }}> <button className={cn( 'inline-flex items-center gap-1.5 whitespace-nowrap align-middle', className )} type="button" {...props} > {children} </button> </TickerContext.Provider> ); });Ticker.displayName = 'Ticker';export type TickerIconProps = HTMLAttributes<HTMLImageElement> & { src: string; symbol: string;};export const TickerIcon = memo( ({ src, symbol, className, ...props }: TickerIconProps) => { if (!src) { return null; } return ( <Avatar className={cn('size-7 border border-border bg-muted p-1', className)} > <AvatarImage src={src} {...props} /> <AvatarFallback className="font-semibold text-muted-foreground text-sm"> {symbol.slice(0, 2).toUpperCase()} </AvatarFallback> </Avatar> ); });TickerIcon.displayName = 'TickerIcon';export type TickerSymbolProps = HTMLAttributes<HTMLSpanElement> & { symbol: string;};export const TickerSymbol = memo( ({ symbol, className, ...props }: TickerSymbolProps) => ( <span className={cn('font-medium', className)} {...props}> {symbol.toUpperCase()} </span> ));TickerSymbol.displayName = 'TickerSymbol';export type TickerPriceProps = HTMLAttributes<HTMLSpanElement> & { price: number;};export const TickerPrice = memo( ({ price, className, ...props }: TickerPriceProps) => { const context = useTickerContext(); const formattedPrice = useMemo( () => context.formatter.format(price), [price, context] ); return ( <span className={cn('text-muted-foreground', className)} {...props}> {formattedPrice} </span> ); });TickerPrice.displayName = 'TickerPrice';export type TickerPriceChangeProps = HTMLAttributes<HTMLSpanElement> & { change: number; isPercent?: boolean;};export const TickerPriceChange = memo( ({ change, isPercent, className, ...props }: TickerPriceChangeProps) => { const isPositiveChange = useMemo(() => change >= 0, [change]); const context = useTickerContext(); const changeFormatted = useMemo(() => { if (isPercent) { return `${change.toFixed(2)}%`; } return context.formatter.format(change); }, [change, isPercent, context]); return ( <span className={cn( 'flex items-center gap-0.5', isPositiveChange ? 'text-green-600 dark:text-green-500' : 'text-red-600 dark:text-red-500', className )} {...props} > <svg aria-labelledby="ticker-change-icon-title" className={isPositiveChange ? '' : 'rotate-180'} fill="currentColor" height="12" role="img" viewBox="0 0 24 24" width="12" xmlns="http://www.w3.org/2000/svg" > <title id="ticker-change-icon-title"> {isPositiveChange ? 'Up icon' : 'Down icon'} </title> <path d="M24 22h-24l12-20z" /> </svg> {changeFormatted} </span> ); });TickerPriceChange.displayName = 'TickerPriceChange';
Use Cases
This free open source React component works well for:
- Trading platforms - Displaying real-time stock prices and market data with color-coded changes for Next.js applications
- Financial dashboards - Showing portfolio performance and asset prices with responsive layouts using TypeScript safety
- Cryptocurrency exchanges - Presenting digital asset prices with percentage changes using Tailwind CSS styling
- Banking applications - Displaying currency rates and exchange information with international locale support using shadcn/ui
- Investment apps - Showing market indices and stock performance with professional ticker displays for JavaScript applications
- Financial news sites - Embedding market data and price tickers with accessible design and mobile optimization
Implementation Notes
- Composable architecture using React component composition for flexible ticker layouts and content organization
- Color-coded styling leveraging Tailwind CSS utilities for automatic red/green price change indicators
- Internationalization supporting ISO 4217 currency codes and IETF BCP 47 locale formatting with proper number display
- TypeScript integration ensuring type safety for financial data structures, price formatting, and locale handling
- Fallback mechanisms implementing smart icon-to-symbol fallback with error handling for missing assets
- shadcn/ui theming using design tokens for consistent styling across financial ticker interfaces
- Performance optimized with efficient rendering and minimal re-renders for real-time price updates
Credit Card
Interactive credit card display components for React and Next.js applications. Built with TypeScript support, Tailwind CSS styling, and shadcn/ui design for professional payment interfaces with flip animations and security features.
Choicebox
Card-style radio and checkbox components for React and Next.js forms. Built with TypeScript support, Tailwind CSS styling, and shadcn/ui design for enhanced form selection interfaces with accessibility features.