Glass Tabs
lore-glass/glass-tabsFreeComponent
Base UI tabs with a liquid-glass selection indicator: a spring-driven lens glides between options, deforming like a droplet with velocity and refracting the highlighted option beneath it.
Live preview
live · rendered by the registry
Rendered by lore-glass on their own page — this is the component running, not a screenshot of it.Install it
npx shadcn@latest add https://loreglasses.com/r/glass-tabs.jsonSource
1"use client";23import { Tabs as TabsPrimitive } from "@base-ui/react/tabs";4import { Glass, isSafariBrowser, useGlassDark } from "@/components/ui/glass";5import {6 MotionValue,7 prefersReducedMotion,8 SpringDriver,9} from "@/components/ui/glass-motion";10import { cn } from "@/lib/utils";11import { useCallback, useEffect, useRef } from "react";1213const PAD_X = 40;14const PAD_Y = 80;15const INDICATOR_SPRING = { stiffness: 50, damping: 13 };16const DEFORM_SPRING = { stiffness: 66, damping: 9 };17const VELOCITY_SCALE = 0.134;18const DEFORM_CLAMP = 0.3;19const SQUEEZE_X = 0.3;20const STRETCH_X = 2;21const RATIO_Y = 4;2223const BG1_LIGHT = "#faf9f9";24const BG1_DARK = "#100f0f";25const BORDER1_LIGHT = "#2e0f0f14";26const BORDER1_DARK = "#ffffff14";27const BODY_LIGHT = "#fff";28const BODY_DARK = "#100f0f";2930function Tabs({ className, ...props }: TabsPrimitive.Root.Props) {31 return (32 <TabsPrimitive.Root33 className={cn("flex flex-col gap-3", className)}34 data-slot="tabs"35 {...props}36 />37 );38}3940interface TabsListProps extends TabsPrimitive.List.Props {41 tint?: number;42}4344function TabsList({45 className,46 children,47 tint = 0,48 ...props49}: TabsListProps) {50 const dark = useGlassDark();51 const safari = isSafariBrowser();52 const listRef = useRef<HTMLDivElement | null>(null);53 const markerRef = useRef<HTMLDivElement | null>(null);5455 const cx = useRef(new MotionValue(0));56 const cy = useRef(new MotionValue(0));57 const hw = useRef(new MotionValue(0));58 const hh = useRef(new MotionValue(0));59 const deform = useRef(new MotionValue(0));60 const initialized = useRef(false);61 const frame = useRef(0);6263 const apply = useCallback(() => {64 frame.current = 0;65 const marker = markerRef.current;66 if (!marker) {67 return;68 }69 const q = Math.max(0, deform.current.get());70 const widthFactor = 1 - q * (q > 0 ? SQUEEZE_X : STRETCH_X);71 const heightFactor = 1 + q * RATIO_Y;72 const w = Math.max(hw.current.get() * 2 * widthFactor, 0);73 const h = Math.max(hh.current.get() * 2 * heightFactor, 0);74 marker.style.width = `${w}px`;75 marker.style.height = `${h}px`;76 marker.style.borderRadius = `${Math.min(w, h) / 2}px`;77 marker.style.left = `${PAD_X + cx.current.get() - w / 2}px`;78 marker.style.top = `${PAD_Y + cy.current.get() - h / 2}px`;79 }, []);8081 const schedule = useCallback(() => {82 if (frame.current === 0) {83 frame.current = requestAnimationFrame(apply);84 }85 }, [apply]);8687// … truncated
What it pulls in
npm packages
Other registry items
Files it writes
More from lore-glass
All 10 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Glassglass | lore-glass | Components | Free | no deps · 4 files | |
| Glass Buttonglass-button | lore-glass | Components | Free | no deps | |
| Glass Dialogglass-dialog | lore-glass | Components | Free | 1 dep | |
| Glass Dockglass-dock | lore-glass | Components | Free | 1 dep | |
| Glass Inputglass-input | lore-glass | Components | Free | 1 dep | |
| Glass Popoverglass-popover | lore-glass | Components | Free | 1 dep | |
| Glass Sliderglass-slider | lore-glass | Components | Free | no deps | |
| Glass Switchglass-switch | lore-glass | Components | Free | no deps |
