Meteors

Decorative meteor shower of streaks falling across the background.

Report a bug

Preview

Switch between light and dark to inspect the embedded Storybook preview.

Installation

pnpm dlx shadcn@latest add https://ui.vllnt.ai/r/meteors.json

Storybook

Explore all variants, controls, and accessibility checks in the interactive Storybook playground.

View in Storybook

2 stories available:

Code

"use client"; import * as React from "react"; import { cn } from "../../lib/utils"; /** Props for {@link Meteors}. */ export type MeteorsProps = React.ComponentPropsWithoutRef<"div"> & { /** Count of meteors streaking across the field. Defaults to `12`. */ count?: number; }; type Meteor = { delay: number; duration: number; left: number; top: number; }; function createMeteors(count: number): Meteor[] { return Array.from({ length: count }, () => ({ delay: Math.random(), duration: 2 + Math.random() * 4, left: Math.random() * 100, top: -(Math.random() * 10), })); } type MeteorStyle = React.CSSProperties & { "--vllnt-meteor-angle"?: string; }; function meteorStyle(meteor: Meteor): MeteorStyle { return { "--vllnt-meteor-angle": "215deg", animation: `vllnt-meteor ${meteor.duration}s linear infinite`, animationDelay: `${meteor.delay}s`, left: `${meteor.left}%`, top: `${meteor.top}%`, }; } /** * Decorative shower of meteors falling diagonally across the field. * * Respects `prefers-reduced-motion`: the meteors stay parked. * * @example * ```tsx * <Meteors count={20} /> * ``` */ export const Meteors = ({ className, count = 12, ref, ...props }: MeteorsProps & { ref?: React.Ref<HTMLDivElement> }) => { const [meteors] = React.useState(() => createMeteors(count)); return ( <div aria-hidden="true" className={cn( "pointer-events-none absolute inset-0 overflow-hidden", className, )} ref={ref} {...props} > {meteors.map((meteor, index) => ( <span className="absolute h-0.5 w-0.5 rounded-full bg-muted-foreground motion-reduce:animate-none" key={index} style={meteorStyle(meteor)} > <span className="absolute right-0 top-1/2 h-px w-12 -translate-y-1/2" style={{ background: "linear-gradient(90deg, oklch(var(--muted-foreground)), transparent)", }} /> </span> ))} </div> ); }; Meteors.displayName = "Meteors";

Dependencies

  • @vllnt/ui@^0.2.1