Medical

Vital Signs Card - Patient Monitoring

Monitor patient vital signs and health metrics. Track continuous monitoring and alert thresholds.

Vital Signs Card

The Vital Signs Card Track patient vital signs monitoring and trends.

Preview

Installation

ash npx shadcn@latest add https://www.vectormotion.dev/registry/vital-signs-card.json

Vital Signs Card
'use client'import React from 'react';import { Activity, Heart } from 'lucide-react';import { motion } from 'motion/react';import { clsx, type ClassValue } from "clsx";import { twMerge } from "tailwind-merge";import { ResponsiveContainer, AreaChart, Area, XAxis, Tooltip, YAxis } from 'recharts';function cn(...inputs: ClassValue[]) {   return twMerge(clsx(inputs));}interface HeartRateData {   t: number;   hr: number;   [key: string]: any;}interface BloodPressureData {   t: number;   bp: number;   [key: string]: any;}interface VitalSignsCardProps {   className?: string;   title?: string;   subtitle?: string;   heartRateData?: HeartRateData[];   bloodPressureData?: BloodPressureData[];   currentHR?: number;   currentBP?: string;}const DEFAULT_HEART_RATE_DATA: HeartRateData[] = [   { t: 1, hr: 72 }, { t: 2, hr: 75 }, { t: 3, hr: 71 }, { t: 4, hr: 73 },   { t: 5, hr: 78 }, { t: 6, hr: 74 }, { t: 7, hr: 72 }, { t: 8, hr: 76 }, { t: 9, hr: 74 }, { t: 10, hr: 73 }];const DEFAULT_BLOOD_PRESSURE_DATA: BloodPressureData[] = [   { t: 1, bp: 118 }, { t: 2, bp: 120 }, { t: 3, bp: 119 }, { t: 4, bp: 122 }, { t: 5, bp: 120 }]const DEFAULT_TITLE = "Vital Signs";const DEFAULT_SUBTITLE = "ICU - Bed 04";const DEFAULT_CURRENT_HR = 72;const DEFAULT_CURRENT_BP = "120/80";export const VitalSignsCard: React.FC<VitalSignsCardProps> = ({   className = "",   title = DEFAULT_TITLE,   subtitle = DEFAULT_SUBTITLE,   heartRateData = DEFAULT_HEART_RATE_DATA,   // bloodPressureData is unused in the original chart rendering for BP, but we keep it for consistency if needed later   bloodPressureData = DEFAULT_BLOOD_PRESSURE_DATA,   currentHR = DEFAULT_CURRENT_HR,   currentBP = DEFAULT_CURRENT_BP,}) => {   const isInteractive = true;   const index = 3;   return (      <motion.div         layoutId={isInteractive ? `card-${index}-${title}` : undefined}         transition={{ duration: 0.4, ease: "easeOut" }}         className={cn(            "relative overflow-hidden rounded-2xl border border-border bg-card text-card-foreground shadow-sm transition-all flex flex-col h-full",            isInteractive ? "hover:border-rose-300 dark:hover:border-rose-700 hover:shadow-md" : "",            className         )}      >         <div className="p-5 flex flex-col h-full relative z-10">            <div className="mb-6 flex items-start justify-between">               <div>                  <h3 className="font-bold text-lg text-foreground">                     {title}                  </h3>                  <div className="flex items-center gap-2 mt-1">                     <span className="relative flex h-2 w-2">                        <span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75"></span>                        <span className="relative inline-flex rounded-full h-2 w-2 bg-emerald-500"></span>                     </span>                     <p className="text-sm text-muted-foreground font-medium">                        {subtitle}                     </p>                  </div>               </div>               <div className="rounded-xl bg-rose-500/10 p-2.5 text-rose-500 dark:text-rose-400 flex items-center justify-center ring-1 ring-rose-100 dark:ring-rose-800">                  <Activity className="h-5 w-5" />               </div>            </div>            <div className="flex-1 grid grid-cols-2 gap-6 relative">               {/* Divider */}               <div className="absolute top-4 bottom-4 left-1/2 w-px bg-gradient-to-b from-transparent via-zinc-200 dark:via-zinc-800 to-transparent transform -translate-x-1/2" />               <div className="flex flex-col justify-between">                  <div>                     <div className="flex items-center gap-2 mb-2">                        <Heart className="h-3.5 w-3.5 text-rose-500 fill-rose-500 animate-pulse" />                        <span className="text-xs font-semibold text-muted-foreground uppercase tracking-wider">Heart Rate</span>                     </div>                     <div className="flex items-baseline gap-1">                        <span className="text-3xl font-bold text-foreground tracking-tight">{currentHR}</span>                        <span className="text-sm font-medium text-muted-foreground">bpm</span>                     </div>                  </div>                  <div className="h-16 w-full -mx-1">                     <ResponsiveContainer width="100%" height="100%">                        <AreaChart data={heartRateData}>                           <defs>                              <linearGradient id="hrGradient" x1="0" y1="0" x2="0" y2="1">                                 <stop offset="5%" stopColor="#f43f5e" stopOpacity={0.2} />                                 <stop offset="95%" stopColor="#f43f5e" stopOpacity={0} />                              </linearGradient>                           </defs>                           <Area type="monotone" dataKey="hr" stroke="#f43f5e" strokeWidth={2} fill="url(#hrGradient)" />                        </AreaChart>                     </ResponsiveContainer>                  </div>               </div>               <div className="flex flex-col justify-between">                  <div>                     <div className="flex items-center gap-2 mb-2">                        <div className="h-3.5 w-3.5 rounded-full border-[3px] border-blue-500/30 bg-blue-500" />                        <span className="text-xs font-semibold text-muted-foreground uppercase tracking-wider">Blood Pres.</span>                     </div>                     <div className="flex items-baseline gap-1">                        <span className="text-3xl font-bold text-foreground tracking-tight">{currentBP?.split('/')[0]}</span>                        <span className="text-xl font-medium text-muted-foreground">/{currentBP?.split('/')[1]}</span>                     </div>                  </div>                  <div className="flex flex-col gap-1.5 mt-auto pb-2">                     <div className="flex justify-between text-[10px] text-zinc-400 font-medium uppercase">                        <span>Sys</span>                        <span>Dia</span>                     </div>                     <div className="h-1.5 w-full bg-zinc-100 dark:bg-zinc-800 rounded-full overflow-hidden">                        <motion.div                           initial={{ width: 0 }}                           animate={{ width: '70%' }}                           transition={{ duration: 1, ease: 'easeOut' }}                           className="h-full bg-gradient-to-r from-blue-400 to-blue-600 rounded-full"                        />                     </div>                     <div className="h-1.5 w-full bg-zinc-100 dark:bg-zinc-800 rounded-full overflow-hidden">                        <motion.div                           initial={{ width: 0 }}                           animate={{ width: '45%' }}                           transition={{ duration: 1, delay: 0.2, ease: 'easeOut' }}                           className="h-full bg-gradient-to-r from-indigo-400 to-indigo-600 rounded-full"                        />                     </div>                  </div>               </div>            </div>         </div>      </motion.div>   );};

Usage

This component is a demo card displaying medical metrics with animated visualizations and dark mode support.

Prop

Type