Animated Sidebar
beui/animated-sidebarFreeComponent
A composable application sidebar with morphing nested navigation that folds into an animated icon rail on desktop and becomes a focus-managed sheet on mobile.
Install it
npx shadcn@latest add https://beui.dev/r/animated-sidebar.jsonSource
1"use client";2// beui.dev/components/motion/animated-sidebar34import { ChevronRight } from "lucide-react";5import {6 AnimatePresence,7 type HTMLMotionProps,8 motion,9 useReducedMotion,10 type Variants,11} from "motion/react";12import {13 type ButtonHTMLAttributes,14 type CSSProperties,15 createContext,16 forwardRef,17 type HTMLAttributes,18 type ReactNode,19 useCallback,20 useContext,21 useEffect,22 useId,23 useRef,24 useState,25 useSyncExternalStore,26} from "react";27import { createPortal } from "react-dom";28import { SharedLayoutBg } from "@/components/motion/shared-layout-bg";29import {30 EASE_DRAWER,31 EASE_OUT,32 SPRING_LAYOUT,33 SPRING_PRESS,34} from "@/lib/ease";35import { cn } from "@/lib/utils";3637type SidebarState = "expanded" | "collapsed";38type SidebarSide = "left" | "right";39type SidebarVariant = "sidebar" | "floating" | "inset";40type SidebarCollapsible = "offcanvas" | "icon" | "none";4142const MOBILE_QUERY = "(max-width: 767px)";43const SIDEBAR_KEYBOARD_SHORTCUT = "b";4445const PANEL_TRANSITION = {46 duration: 0.36,47 ease: EASE_DRAWER,48} as const;4950// The desktop rail settles at a hard zero-width boundary. Keep the spring51// critically damped so it cannot overshoot, pause against that boundary, and52// then snap back during the final frame.53const SIDEBAR_MORPH_TRANSITION = {54 type: "spring",55 stiffness: 380,56 damping: 35,57 mass: 0.75,58} as const;5960const LABEL_ENTER_TRANSITION = {61 duration: 0.2,62 delay: 0.08,63 ease: EASE_OUT,64} as const;6566const LABEL_EXIT_TRANSITION = {67 duration: 0.12,68 ease: EASE_OUT,69} as const;7071const SUBMENU_TRANSITION = {72 duration: 0.18,73 ease: EASE_OUT,74} as const;7576const SUBMENU_VARIANTS: Variants = {77 closed: {78 opacity: 0,79 clipPath: "inset(0 0 100% 0 round 8px)",80 transition: {81 duration: 0.14,82 ease: EASE_OUT,83 staggerChildren: 0.025,84 staggerDirection: -1,85 },86 },87 open: {88 opacity: 1,89 clipPath: "inset(0 0 0% 0 round 8px)",90 transition: {91 duration: 0.2,92 delayChildren: 0.035,93 ease: EASE_OUT,94 staggerChildren: 0.045,95 },96 },97};9899const SUBMENU_ITEM_VARIANTS: Variants = {100 closed: {101 opacity: 0,102 y: -6,103 filter: "blur(3px)",104 },105 open: {106 opacity: 1,107 y: 0,108 filter: "blur(0px)",109 transition: SUBMENU_TRANSITION,110 },111};112113const REDUCED_TRANSITION = {114 duration: 0.16,115 ease: EASE_OUT,116} as const;117118const FOCUSABLE_SELECTOR = [119 "a[href]",120 "button:not([disabled])",121 "input:not([disabled])",122 "select:not([disabled])",123// … truncated
What it pulls in
npm packages
Files it writes
More from beui
All 104 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Action Swap Bluraction-swap-blur | beui | Components | Free | 3 deps · 4 files | |
| Action Swap Cascadeaction-swap-cascade | beui | Components | Free | 3 deps · 4 files | |
| Action Swap Rollaction-swap-roll | beui | Components | Free | 3 deps · 4 files | |
| Agent Activityagent-activity | beui | Components | Free | 4 deps · 9 files | |
| Agent Loading States Agent Progressagent-progress | beui | Components | Free | 3 deps · 3 files | |
| AI Sidebarai-sidebar | beui | Components | Free | 4 deps · 5 files | |
| Animated Badgeanimated-badge | beui | Components | Free | 4 deps · 3 files | |
| Number Animation Animated Numberanimated-number | beui | Components | Free | 3 deps · 3 files |
