Back to Activity
Components / Activity
Focus Breath
Free Activity React component — FocusBreathWidget. MIT licensed, copy-paste ready for Next.js and Tailwind CSS.
A breathing guide that pulses between inhale and exhale every four seconds. One tap to start — useful in wellness or focus flows.
Preview
inhale
Breathe
Setup
How to use
Free for personal and commercial use. No UI attribution required. Include the MIT copyright notice when copying component source into your project. Read the MIT license.
- 1
Run in your terminal:
$npm install clsx tailwind-merge lucide-react - 2
Copy
lib/cn.tsbelow. Skip if you already havecn(). - 3
Copy the code below and create
components/activity/focus-breath-widget.tsxin your project. - 4
Import and render:
Exampleimport { FocusBreathWidget } from "@/components/activity/focus-breath-widget";
lib/cn.ts
import { type ClassValue, clsx } from "clsx";import { twMerge } from "tailwind-merge";export function cn(...inputs: ClassValue[]) {return twMerge(clsx(inputs));}
components/activity/focus-breath-widget.tsx
"use client";import {forwardRef,useEffect,useState,type ComponentPropsWithoutRef,} from "react";import { cn } from "@/lib/cn";export type FocusBreathWidgetProps = Readonly<{// Title shown below the breathing circle.label?: string;} & ComponentPropsWithoutRef<"div">>;// Guided breathing widget — circle grows on inhale and shrinks on exhaleexport const FocusBreathWidget = forwardRef<HTMLDivElement,FocusBreathWidgetProps>(({ className, label = "Breathe", ...props }, ref) => {// Alternates every 4 seconds between inhale and exhaleconst [phase, setPhase] = useState<"inhale" | "exhale">("inhale");// Circle scale: larger on inhale, smaller on exhaleconst [scale, setScale] = useState(0.85);// Flip inhale ↔ exhale on a fixed 4s rhythmuseEffect(() => {const timer = globalThis.setInterval(() => {setPhase((p) => (p === "inhale" ? "exhale" : "inhale"));}, 4000);return () => globalThis.clearInterval(timer);}, []);// Resize the outer ring whenever the phase changesuseEffect(() => {setScale(phase === "inhale" ? 1 : 0.75);}, [phase]);// Outer ring scales with the breathe cycle; inner circle shows inhale/exhale labelreturn (<divref={ref}data-slot="focus-breath-widget"className={cn("flex h-44 w-44 flex-col items-center justify-center overflow-hidden rounded-3xl border border-neutral-100 bg-white font-sans shadow-lg shadow-black/5 select-none",className,)}{...props}><divclassName="flex h-24 w-24 items-center justify-center rounded-full bg-neutral-100 transition-transform duration-4000 ease-in-out"style={{ transform: `scale(${scale})` }}><div className="flex h-16 w-16 items-center justify-center rounded-full border border-neutral-100 bg-white"><spanclassName="text-[10px] font-semibold tracking-widest text-neutral-500 uppercase"aria-live="polite">{phase}</span></div></div><p className="mt-3 text-xs font-medium text-neutral-600">{label}</p></div>);});FocusBreathWidget.displayName = "FocusBreathWidget";