A meteor-shower background of glowing streaks falling at a configurable angle, driven entirely by CSS keyframes with deterministic per-meteor positions and timing.
Preview
Meteor Shower
Pure CSS streaks with deterministic timing.
Customize
4 propsInstallation
Copies the component source and its CSS Module into your project.
npx motionfoundry add meteorsWrites components/ui/meteors.tsx and components/styles/meteors.module.css.
Usage
Updates live as you change the controls — only props that differ from the defaults are included.
1import { Meteors } from "@motionfoundry/ui";2 3export function Example() {4 return (5 <Meteors />6 );7}Props
Accessibility
The meteor layer is aria-hidden and pointer-events: none; foreground children keep their own semantics. Positions and timings come from a seeded rand(n) so SSR markup is stable. Under prefers-reduced-motion the animation is removed and meteors render as faint static streaks.
Toggle prefers-reduced-motion in your OS or browser devtools (Rendering → Emulate CSS media feature) to verify the reduced-motion state.
Source
1"use client";2 3import * as React from "react";4import styles from "../styles/meteors.module.css";5 6export interface MeteorsProps {7 /** Number of meteors in the shower */8 count?: number;9 /** Speed multiplier (1 = base 2–5s fall per meteor) */10 speed?: number;11 /** Travel direction in degrees, clockwise from pointing right (135 = down-left) */12 angle?: number;13 /** Meteor head and trail color */14 meteorColor?: string;15 /** Foreground content rendered above the shower */16 children?: React.ReactNode;17 /** Optional className merged onto the root */18 className?: string;19}20 21const rand = (n: number) => {22 const x = Math.sin(n * 9301 + 49297) * 233280;23 return x - Math.floor(x);24};25 26export function Meteors({27 count = 14,28 speed = 1,29 angle = 135,30 meteorColor = "#38bdf8",31 children,32 className = "",33}: MeteorsProps) {34 const safeSpeed = speed > 0 ? speed : 1;35 const total = Math.max(0, Math.round(count));36 const styleVars = {37 "--mf-mt-color": meteorColor,38 "--mf-mt-angle": `${angle}deg`,39 } as React.CSSProperties;40 41 return (42 <div style={styleVars} className={`${styles.root} ${className}`.trim()}>43 <div aria-hidden="true" className={styles.sky}>44 {Array.from({ length: total }, (_, i) => (45 <span46 key={i}47 className={styles.meteor}48 style={{49 left: `${(rand(i) * 130 - 10).toFixed(2)}%`,50 top: `${(rand(i + 7) * 50 - 15).toFixed(2)}%`,51 animationDuration: `${((2 + rand(i + 1) * 3) / safeSpeed).toFixed(2)}s`,52 animationDelay: `-${(rand(i + 2) * 5).toFixed(2)}s`,53 }}54 />55 ))}56 </div>57 <div className={styles.content}>58 {children ?? (59 <div className={styles.copy}>60 <h3 className={styles.title}>Meteor Shower</h3>61 <p className={styles.subtitle}>Pure CSS streaks with deterministic timing.</p>62 </div>63 )}64 </div>65 </div>66 );67}