Sidebar Rails Components

A sidebar component for your Ruby on Rails application. The toggle is handled by a button in the top right corner of the sidebar, and the status is kept in the local storage. It is of course fully responsive and works on mobile devices.

Installation

1. Stimulus Controller Setup

Start by adding the following controller to your project:

import { Controller } from "@hotwired/stimulus";

// Connects to data-controller="sidebar"
export default class extends Controller {
  static targets = [
    "desktopSidebar",
    "mobileSidebar",
    "contentTemplate",
    "sharedContent",
    "desktopContent",
    "mobileBackdrop",
    "mobilePanel",
  ];
  static values = {
    storageKey: { type: String, default: "sidebarOpen" },
  };

  connect() {
    // Bind the handler once so we can properly remove it later
    this.boundHandleToggle = this.handleToggle.bind(this);

    // Clone the sidebar content for both mobile and desktop
    if (this.hasContentTemplateTarget) {
      // Clone content for desktop (only if not already cloned)
      // Check if nav element exists (the actual cloned content)
      if (this.hasDesktopContentTarget && !this.desktopContentTarget.querySelector("nav")) {
        const desktopClone = this.contentTemplateTarget.content.cloneNode(true);
        this.desktopContentTarget.appendChild(desktopClone);
      }

      // Clone content for mobile (only if not already cloned)
      if (this.hasSharedContentTarget && !this.sharedContentTarget.querySelector("nav")) {
        const mobileClone = this.contentTemplateTarget.content.cloneNode(true);
        this.sharedContentTarget.appendChild(mobileClone);

        // Update the close button in mobile view to show X icon and have mobile close action
        const mobileNav = this.sharedContentTarget.querySelector("nav");
        if (mobileNav) {
          const closeButton = mobileNav.querySelector('[data-action*="sidebar#close"]');
          if (closeButton) {
            closeButton.setAttribute("data-action", "click->sidebar#closeMobile");
            closeButton.classList.remove("cursor-w-resize");
            closeButton.setAttribute("aria-label", "Close sidebar");

            // Replace the collapse icon with an X icon
            const svg = closeButton.querySelector("svg");
            if (svg) {
              svg.innerHTML =
                '<g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><line x1="13.25" y1="4.75" x2="4.75" y2="13.25"></line><line x1="13.25" y1="13.25" x2="4.75" y2="4.75"></line></g>';
            }
          }
        }
      }
    }

    if (this.hasDesktopSidebarTarget) {
      // Temporarily disable transitions to prevent animation on page load
      this.desktopSidebarTarget.style.transition = "none";

      // Restore the saved state from localStorage, default to open if no saved state
      const savedState = localStorage.getItem(this.storageKeyValue);

      if (savedState !== null) {
        this.desktopSidebarTarget.open = savedState === "true";
      } else {
        // Default to open for first-time visitors
        this.desktopSidebarTarget.open = true;
      }

      // Re-enable transitions after a brief delay
      requestAnimationFrame(() => {
        requestAnimationFrame(() => {
          this.desktopSidebarTarget.style.transition = "";
        });
      });

      // Listen for toggle events to save the state
      this.desktopSidebarTarget.addEventListener("toggle", this.boundHandleToggle);
    }
  }

  disconnect() {
    if (this.hasDesktopSidebarTarget && this.boundHandleToggle) {
      this.desktopSidebarTarget.removeEventListener("toggle", this.boundHandleToggle);
    }
  }

  handleToggle(event) {
    // Save the current state to localStorage
    localStorage.setItem(this.storageKeyValue, this.desktopSidebarTarget.open.toString());
  }

  open() {
    if (this.hasDesktopSidebarTarget) {
      this.desktopSidebarTarget.open = true;
    }
  }

  close() {
    if (this.hasDesktopSidebarTarget) {
      this.desktopSidebarTarget.open = false;
    }
  }

  toggle() {
    if (this.hasDesktopSidebarTarget) {
      this.desktopSidebarTarget.open = !this.desktopSidebarTarget.open;
    }
  }

  openMobile() {
    if (this.hasMobileSidebarTarget) {
      // Set initial hidden states
      if (this.hasMobileBackdropTarget) {
        this.mobileBackdropTarget.style.opacity = "0";
      }
      if (this.hasMobilePanelTarget) {
        this.mobilePanelTarget.style.transform = "translateX(-100%)";
      }

      // Remove hidden class
      this.mobileSidebarTarget.classList.remove("hidden");

      // Trigger transition on next frame
      requestAnimationFrame(() => {
        if (this.hasMobileBackdropTarget) {
          this.mobileBackdropTarget.style.opacity = "1";
        }
        if (this.hasMobilePanelTarget) {
          this.mobilePanelTarget.style.transform = "translateX(0)";
        }
      });
    }
  }

  closeMobile() {
    if (this.hasMobileSidebarTarget) {
      // Trigger closing transition
      if (this.hasMobileBackdropTarget) {
        this.mobileBackdropTarget.style.opacity = "0";
      }
      if (this.hasMobilePanelTarget) {
        this.mobilePanelTarget.style.transform = "translateX(-100%)";
      }

      // Wait for transition to complete before hiding
      setTimeout(() => {
        if (this.hasMobileSidebarTarget) {
          this.mobileSidebarTarget.classList.add("hidden");
        }
      }, 300); // Match the transition duration
    }
  }
}

I also recommend adding the tooltip controller to your project if you haven't already:

import { Controller } from "@hotwired/stimulus";
import { computePosition, offset, flip, shift, arrow, autoUpdate } from "@floating-ui/dom";

// Global tooltip state manager for intelligent behavior
class TooltipGlobalState {
  constructor() {
    this.visibleCount = 0;
    this.isFastMode = false;
    this.resetTimeout = null;
    this.fastModeResetDelay = 100; // 0.1 seconds
    this.visibleTooltips = new Set(); // Track currently visible tooltip controllers
    this.closingTooltips = new Set(); // Track tooltips currently in closing animation
  }

  // Called when a tooltip becomes visible
  onTooltipShow(tooltipController) {
    this.visibleTooltips.add(tooltipController);
    this.closingTooltips.delete(tooltipController); // Remove from closing if it was there
    this.visibleCount = this.visibleTooltips.size;
    if (this.visibleCount > 0 && !this.isFastMode) {
      this.isFastMode = true;
    }
    this.clearResetTimeout();
  }

  // Called when a tooltip starts hiding
  onTooltipStartHide(tooltipController) {
    this.visibleTooltips.delete(tooltipController);
    this.visibleCount = this.visibleTooltips.size;
  }

  // Called when a tooltip starts its closing animation
  onTooltipClosing(tooltipController) {
    this.closingTooltips.add(tooltipController);
  }

  // Called when a tooltip has fully closed
  onTooltipClosed(tooltipController) {
    this.closingTooltips.delete(tooltipController);

    // If no tooltips are visible or closing, start countdown to exit fast mode
    if (this.visibleCount === 0 && this.closingTooltips.size === 0) {
      this.startResetTimeout();
    }
  }

  // Hide all currently visible tooltips and interrupt closing animations
  hideAllTooltipsInstantly(exceptController) {
    // Instantly hide all visible tooltips
    const visibleToHide = [...this.visibleTooltips].filter((controller) => controller !== exceptController);
    visibleToHide.forEach((controller) => {
      controller._hideTooltip(true); // true = instant hide
    });

    // Instantly finish all closing animations
    const closingToHide = [...this.closingTooltips].filter((controller) => controller !== exceptController);
    closingToHide.forEach((controller) => {
      controller._finishClosingAnimation();
    });
  }

  // Check if we're in fast mode
  isInFastMode() {
    return this.isFastMode;
  }

  // Start timeout to reset fast mode
  startResetTimeout() {
    this.clearResetTimeout();
    this.resetTimeout = setTimeout(() => {
      this.isFastMode = false;
    }, this.fastModeResetDelay);
  }

  // Clear the reset timeout
  clearResetTimeout() {
    if (this.resetTimeout) {
      clearTimeout(this.resetTimeout);
      this.resetTimeout = null;
    }
  }
}

// Global instance
const tooltipGlobalState = new TooltipGlobalState();

export default class extends Controller {
  // placement and offset can still be configured via data-tooltip-placement-value etc. if desired,
  // but will use defaults if the original HTML (using data-tooltip-*) doesn't provide them.
  static values = {
    placement: { type: String, default: "top" }, // Placement(s) of the tooltip, e.g., "top", "top-start", "top-end", "bottom", "bottom-start", "bottom-end", "left", "left-start", "left-end", "right", "right-start", "right-end"
    offset: { type: Number, default: 8 }, // Offset of the tooltip
    maxWidth: { type: Number, default: 200 }, // Default max width for tooltips
    delay: { type: Number, default: 0 }, // Delay before showing the tooltip (in ms)
    size: { type: String, default: "regular" }, // Size of the tooltip, e.g., "small", "regular", "large"
    animation: { type: String, default: "fade" }, // e.g., "fade", "origin", "fade origin", "none"
    trigger: { type: String, default: "auto" }, // space-separated: mouseenter, focus, click, or "auto" (auto-detects based on device)
    // tooltipContent and tooltipArrow are read directly from element attributes in connect()
  };

  _hasAnimationType(type) {
    return this.animationValue.split(" ").includes(type);
  }

  connect() {
    this.tooltipContent = this.element.getAttribute("data-tooltip-content") || "";
    this.showArrow = this.element.getAttribute("data-tooltip-arrow") !== "false";
    this.showTimeoutId = null;
    this.hideTimeoutId = null;
    this.isVisible = false;

    if (!this.tooltipContent) {
      console.warn("Tooltip initialized without data-tooltip-content", this.element);
      return;
    }

    this.tooltipElement = document.createElement("div");
    this.tooltipElement.className =
      "tooltip-content pointer-events-none wrap-break-word shadow-sm border rounded-lg border-white/10 absolute bg-[#333333] text-white py-1 px-2 z-[1000]";

    const sizeClasses = {
      small: "text-xs",
      regular: "text-sm",
      large: "text-base",
    };
    const sizeClass = sizeClasses[this.sizeValue] || sizeClasses.regular;
    this.tooltipElement.classList.add(sizeClass);

    // Always start transparent and hidden. Visibility/opacity managed by show/hide logic.
    this.tooltipElement.classList.add("opacity-0");
    this.tooltipElement.style.visibility = "hidden";

    // Base transition for all animations that might use opacity or transform
    if (this._hasAnimationType("fade") || this._hasAnimationType("origin")) {
      this.tooltipElement.classList.add("transition-all"); // Use transition-all for simplicity if combining
    }

    if (this._hasAnimationType("fade")) {
      // Ensure specific duration for opacity if not covered by a general one or if different
      this.tooltipElement.classList.add("duration-150"); // Default fade duration
    }
    if (this._hasAnimationType("origin")) {
      // Ensure specific duration for transform if not covered by a general one or if different
      this.tooltipElement.classList.add("duration-150", "ease-out"); // Default origin duration and ease
      this.tooltipElement.classList.add("scale-95"); // Initial state for origin animation
    }

    this.tooltipElement.innerHTML = this.tooltipContent;
    this.tooltipElement.style.maxWidth = `${this.maxWidthValue}px`;

    if (this.showArrow) {
      // Create arrow container with padding to prevent clipping at viewport edges
      this.arrowContainer = document.createElement("div");
      this.arrowContainer.className = "absolute z-[1000]";

      // Create the arrow element within the container
      this.arrowElement = document.createElement("div");
      this.arrowElement.className = "tooltip-arrow-element bg-[#333333] w-2 h-2 border-white/10";
      this.arrowElement.style.transform = "rotate(45deg)";

      this.arrowContainer.appendChild(this.arrowElement);
      this.tooltipElement.appendChild(this.arrowContainer);
    }

    // Append target logic is handled in _showTooltip to ensure it's correct at showtime
    // const appendTarget = this.element.closest("dialog[open]") || document.body;
    // appendTarget.appendChild(this.tooltipElement);

    this.showTooltipBound = this._showTooltip.bind(this);
    this.hideTooltipBound = this._hideTooltip.bind(this);
    this.clickHideTooltipBound = this._handleClick.bind(this);
    this.clickToggleTooltipBound = this._handleClickToggle.bind(this);
    this.clickOutsideBound = this._handleClickOutside.bind(this);

    // Auto-detect trigger based on device capability
    let triggerValue = this.triggerValue;
    if (triggerValue === "auto") {
      // Use click on touch devices, mouseenter+focus on others
      const isTouchDevice = "ontouchstart" in window || navigator.maxTouchPoints > 0;
      triggerValue = isTouchDevice ? "click" : "mouseenter focus";
    }

    const triggers = triggerValue.split(" ");
    this.hasMouseEnterTrigger = triggers.includes("mouseenter");
    this.hasClickTrigger = triggers.includes("click");

    triggers.forEach((event_type) => {
      if (event_type === "mouseenter") {
        this.element.addEventListener("mouseenter", this.showTooltipBound);
        this.element.addEventListener("mouseleave", this.hideTooltipBound);
      }
      if (event_type === "focus") {
        this.element.addEventListener("focus", this.showTooltipBound);
        this.element.addEventListener("blur", this.hideTooltipBound);
      }
      if (event_type === "click") {
        this.element.addEventListener("click", this.clickToggleTooltipBound);
      }
    });

    // Add click event to close tooltip but allow event to bubble up (only for hover triggers)
    if (this.hasMouseEnterTrigger && !this.hasClickTrigger) {
      this.element.addEventListener("click", this.clickHideTooltipBound);
    }

    this.cleanupAutoUpdate = null;
    this.intersectionObserver = null;
  }

  disconnect() {
    clearTimeout(this.showTimeoutId);
    clearTimeout(this.hideTimeoutId);

    // Remove from global state if visible or closing
    if (this.isVisible) {
      tooltipGlobalState.onTooltipStartHide(this);
      this.isVisible = false;
    }
    tooltipGlobalState.onTooltipClosed(this);

    // Auto-detect trigger (same logic as connect)
    let triggerValue = this.triggerValue;
    if (triggerValue === "auto") {
      const isTouchDevice = "ontouchstart" in window || navigator.maxTouchPoints > 0;
      triggerValue = isTouchDevice ? "click" : "mouseenter focus";
    }

    triggerValue.split(" ").forEach((event_type) => {
      if (event_type === "mouseenter") {
        this.element.removeEventListener("mouseenter", this.showTooltipBound);
        this.element.removeEventListener("mouseleave", this.hideTooltipBound);
      }
      if (event_type === "focus") {
        this.element.removeEventListener("focus", this.showTooltipBound);
        this.element.removeEventListener("blur", this.hideTooltipBound);
      }
      if (event_type === "click") {
        this.element.removeEventListener("click", this.clickToggleTooltipBound);
      }
    });

    // Remove click event listener only if it was added for hover-enabled tooltips
    if (this.hasMouseEnterTrigger && !this.hasClickTrigger) {
      this.element.removeEventListener("click", this.clickHideTooltipBound);
    }

    this._cleanupObservers();

    if (this.tooltipElement && this.tooltipElement.parentElement) {
      this.tooltipElement.remove();
    }
  }

  async _updatePositionAndArrow() {
    if (!this.element || !this.tooltipElement) return;

    // Parse placement value to support multiple placements
    const placements = this.placementValue.split(/[\s,]+/).filter(Boolean);
    const primaryPlacement = placements[0] || "top";
    const fallbackPlacements = placements.slice(1);

    const middleware = [
      offset(this.offsetValue),
      flip({
        fallbackPlacements: fallbackPlacements.length > 0 ? fallbackPlacements : undefined,
      }),
      shift({ padding: 5 }),
    ];
    if (this.showArrow && this.arrowContainer) {
      middleware.push(arrow({ element: this.arrowContainer, padding: 2 }));
    }

    const { x, y, placement, middlewareData } = await computePosition(this.element, this.tooltipElement, {
      placement: primaryPlacement,
      middleware: middleware,
    });

    Object.assign(this.tooltipElement.style, {
      left: `${x}px`,
      top: `${y}px`,
    });

    if (this._hasAnimationType("origin")) {
      const basePlacement = placement.split("-")[0];
      this.tooltipElement.classList.remove("origin-top", "origin-bottom", "origin-left", "origin-right");
      if (basePlacement === "top") {
        this.tooltipElement.classList.add("origin-bottom");
      } else if (basePlacement === "bottom") {
        this.tooltipElement.classList.add("origin-top");
      } else if (basePlacement === "left") {
        this.tooltipElement.classList.add("origin-right");
      } else if (basePlacement === "right") {
        this.tooltipElement.classList.add("origin-left");
      }
    }

    if (this.showArrow && this.arrowContainer && this.arrowElement && middlewareData.arrow) {
      const { x: arrowX, y: arrowY } = middlewareData.arrow;
      const currentPlacement = placement; // Use the resolved placement from computePosition
      const basePlacement = currentPlacement.split("-")[0];
      const staticSide = {
        top: "bottom",
        right: "left",
        bottom: "top",
        left: "right",
      }[basePlacement];

      // Apply appropriate padding based on placement direction
      this.arrowContainer.classList.remove("px-1", "py-1");
      if (basePlacement === "top" || basePlacement === "bottom") {
        this.arrowContainer.classList.add("px-1"); // Horizontal padding for top/bottom
      } else {
        this.arrowContainer.classList.add("py-1"); // Vertical padding for left/right
      }

      // Position the arrow container
      Object.assign(this.arrowContainer.style, {
        left: arrowX != null ? `${arrowX}px` : "",
        top: arrowY != null ? `${arrowY}px` : "",
        right: "",
        bottom: "",
        [staticSide]: "-0.275rem", // Adjusted to -0.275rem as often seen with 0.5rem arrows
      });

      // Style the arrow element within the container
      // Reset existing border classes before adding new ones
      this.arrowElement.classList.remove("border-t", "border-r", "border-b", "border-l");

      // Apply new borders based on placement
      if (staticSide === "bottom") {
        // Arrow points up
        this.arrowElement.classList.add("border-b", "border-r");
      } else if (staticSide === "top") {
        // Arrow points down
        this.arrowElement.classList.add("border-t", "border-l");
      } else if (staticSide === "left") {
        // Arrow points right
        this.arrowElement.classList.add("border-b", "border-l");
      } else if (staticSide === "right") {
        // Arrow points left
        this.arrowElement.classList.add("border-t", "border-r");
      }
    }
  }

  async _showTooltip() {
    if (!this.tooltipElement) return;

    clearTimeout(this.hideTimeoutId); // Cancel any pending hide finalization
    clearTimeout(this.showTimeoutId); // Cancel any pending show

    // Always hide all other visible tooltips and interrupt closing animations IMMEDIATELY
    // This must happen synchronously before scheduling the show to prevent multiple tooltips
    // from appearing simultaneously when hovering quickly
    tooltipGlobalState.hideAllTooltipsInstantly(this);

    // Determine if we should use fast mode (no delay, no animations)
    const isFastMode = tooltipGlobalState.isInFastMode();
    const effectiveDelay = isFastMode ? 0 : this.delayValue;

    this.showTimeoutId = setTimeout(async () => {
      // Ensure tooltip is appended to the correct target (body or open dialog)
      // This is done here to handle cases where the element might move into/out of a dialog
      const currentAppendTarget = this.element.closest("dialog[open]") || document.body;
      if (this.tooltipElement.parentElement !== currentAppendTarget) {
        currentAppendTarget.appendChild(this.tooltipElement);
      }

      // Tooltip is already opacity-0 and visibility-hidden from connect()
      // 1. Calculate and apply position
      await this._updatePositionAndArrow();

      // 2. Make it visible
      this.tooltipElement.style.visibility = "visible";

      // 3. Apply opacity and scale based on animation type
      const applyVisibleState = () => {
        this.tooltipElement.classList.remove("opacity-0");
        this.tooltipElement.classList.add("opacity-100");

        if (this._hasAnimationType("origin")) {
          this.tooltipElement.classList.remove("scale-95");
          this.tooltipElement.classList.add("scale-100");
        }
      };

      if (isFastMode) {
        // Fast mode: apply changes instantly without transitions
        this.tooltipElement.setAttribute("data-instant", "");
        this._withoutTransition(applyVisibleState);
        // Remove data-instant after the instant show is complete
        // This prevents it from interfering with hover state changes
        requestAnimationFrame(() => {
          if (this.tooltipElement) {
            this.tooltipElement.removeAttribute("data-instant");
          }
        });
      } else {
        // Normal mode: use requestAnimationFrame for smooth animations
        this.tooltipElement.removeAttribute("data-instant");
        requestAnimationFrame(applyVisibleState);
      }

      // 4. Setup autoUpdate for continuous positioning
      if (this.cleanupAutoUpdate) {
        this.cleanupAutoUpdate();
      }
      this.cleanupAutoUpdate = autoUpdate(
        this.element,
        this.tooltipElement,
        async () => {
          // Re-check append target in case DOM changes during interaction
          const appendTargetRecurring = this.element.closest("dialog[open]") || document.body;
          if (this.tooltipElement.parentElement !== appendTargetRecurring) {
            appendTargetRecurring.appendChild(this.tooltipElement);
          }
          await this._updatePositionAndArrow();
        },
        { animationFrame: true } // Use animationFrame for smoother updates
      );

      // 5. Setup intersection observer to hide tooltip when trigger element goes out of view
      if (this.intersectionObserver) {
        this.intersectionObserver.disconnect();
      }
      this.intersectionObserver = new IntersectionObserver(
        (entries) => {
          entries.forEach((entry) => {
            if (!entry.isIntersecting) {
              this._hideTooltip();
            }
          });
        },
        { threshold: 0 } // Hide as soon as any part goes out of view
      );
      this.intersectionObserver.observe(this.element);

      // 6. Register with global state that this tooltip is now visible
      if (!this.isVisible) {
        this.isVisible = true;
        tooltipGlobalState.onTooltipShow(this);
      }

      // 7. Add click-outside listener for click triggers
      if (this.hasClickTrigger) {
        // Use setTimeout to avoid immediately triggering the click-outside handler
        setTimeout(() => {
          document.addEventListener("click", this.clickOutsideBound);
        }, 0);
      }
    }, effectiveDelay);
  }

  _handleClick() {
    // Hide the tooltip but allow the event to bubble up
    this._hideTooltip();
    // Don't call event.preventDefault() or event.stopPropagation()
    // so the event can bubble up to parent elements (like kanban cards)
  }

  _handleClickToggle(event) {
    // Toggle tooltip visibility on click
    if (this.isVisible) {
      this._hideTooltip();
    } else {
      this._showTooltip();
    }

    // Prevent the click from bubbling only if the element is interactive (button, a, etc.)
    // Otherwise allow it to bubble for non-interactive elements
    const isInteractive = this.element.matches("button, a, [role='button'], input, select, textarea");
    if (!isInteractive) {
      event.stopPropagation();
    }
  }

  _handleClickOutside(event) {
    // Hide tooltip when clicking outside of trigger element
    if (!this.element.contains(event.target)) {
      this._hideTooltip();
    }
  }

  // Helper: Apply instant changes without transitions
  _withoutTransition(callback) {
    if (!this.tooltipElement) return;

    // Ensure data-instant is set (caller should set it, but we ensure it's there)
    this.tooltipElement.setAttribute("data-instant", "");
    this.tooltipElement.offsetHeight; // Force reflow

    callback();

    // Don't remove data-instant here - let the show/hide logic manage it
    // This keeps the attribute on for the entire duration of instant mode
  }

  // Helper: Apply hidden state (opacity, scale, visibility)
  _applyHiddenState() {
    if (!this.tooltipElement) return;

    this.tooltipElement.classList.remove("opacity-100");
    this.tooltipElement.classList.add("opacity-0");
    if (this._hasAnimationType("origin")) {
      this.tooltipElement.classList.remove("scale-100");
      this.tooltipElement.classList.add("scale-95");
    }
    this.tooltipElement.style.visibility = "hidden";
  }

  // Helper: Cleanup observers and auto-update
  _cleanupObservers() {
    if (this.cleanupAutoUpdate) {
      this.cleanupAutoUpdate();
      this.cleanupAutoUpdate = null;
    }
    if (this.intersectionObserver) {
      this.intersectionObserver.disconnect();
      this.intersectionObserver = null;
    }
    // Remove click-outside listener
    if (this.hasClickTrigger) {
      document.removeEventListener("click", this.clickOutsideBound);
    }
  }

  _hideTooltip(isInstantHide = false) {
    clearTimeout(this.showTimeoutId); // Cancel any pending show operation
    clearTimeout(this.hideTimeoutId); // Cancel any pending hide finalization

    if (!this.tooltipElement) return;

    // Register with global state that this tooltip is starting to hide
    if (this.isVisible) {
      this.isVisible = false;
      tooltipGlobalState.onTooltipStartHide(this);
    }

    this._cleanupObservers();

    if (isInstantHide) {
      // Instant hide: apply hidden state without transitions
      this.tooltipElement.setAttribute("data-instant", "");
      this._withoutTransition(() => {
        this._applyHiddenState();
      });
      tooltipGlobalState.onTooltipClosed(this);
      return;
    }

    // Remove data-instant for normal animated hide
    this.tooltipElement.removeAttribute("data-instant");

    // Normal hide with animations
    tooltipGlobalState.onTooltipClosing(this);

    const needsAnimation = this._hasAnimationType("fade") || this._hasAnimationType("origin");

    if (needsAnimation || this.animationValue === "none") {
      // Apply opacity/scale changes for animation
      this.tooltipElement.classList.remove("opacity-100");
      this.tooltipElement.classList.add("opacity-0");
      if (this._hasAnimationType("origin")) {
        this.tooltipElement.classList.remove("scale-100");
        this.tooltipElement.classList.add("scale-95");
      }
    }

    // Calculate animation duration
    const animationDelay = needsAnimation ? 100 : 0; // Both fade and origin use 100ms

    this.hideTimeoutId = setTimeout(() => {
      if (this.tooltipElement) {
        this.tooltipElement.style.visibility = "hidden";
      }
      tooltipGlobalState.onTooltipClosed(this);
    }, animationDelay);
  }

  // Instantly finish a closing animation (called when another tooltip is being shown)
  _finishClosingAnimation() {
    clearTimeout(this.hideTimeoutId);

    if (!this.tooltipElement) return;

    this.tooltipElement.setAttribute("data-instant", "");
    this._withoutTransition(() => {
      this._applyHiddenState();
    });

    tooltipGlobalState.onTooltipClosed(this);
  }
}

Examples

Basic Sidebar

A basic sidebar component with the ability to minimize and maximize it.

Your content
<!-- Note: You will likely need to replace all the min-h-[600px] with a min-h-screen -->

<div class="flex h-full w-full flex-col relative" data-controller="sidebar" data-sidebar-storage-key-value="basicSidebar">
  <!-- Mobile Sidebar Overlay -->
  <div class="absolute inset-0 z-50 md:hidden hidden" data-sidebar-target="mobileSidebar">
    <!-- Backdrop -->
    <div class="absolute inset-0 bg-black/50 transition-opacity duration-300" data-sidebar-target="mobileBackdrop" data-action="click->sidebar#closeMobile"></div>

    <!-- Sidebar Panel -->
    <div class="absolute inset-y-0 left-0 w-64 bg-neutral-50 dark:bg-neutral-900 shadow-xl border-r border-neutral-200 dark:border-neutral-800 transition-transform duration-300 ease-out" data-sidebar-target="mobilePanel">
      <div data-sidebar-target="sharedContent"></div>
    </div>
  </div>

  <div class="relative z-0 flex h-full w-full">
    <div class="relative flex h-full flex-row">
      <details class="w-13 open:w-64 group/sidebar relative z-20 h-full shrink-0 overflow-hidden border-r border-neutral-200 bg-neutral-50 max-md:hidden motion-safe:transition-all motion-safe:duration-300 dark:border-neutral-800 dark:bg-neutral-900" data-sidebar-target="desktopSidebar">
        <!-- Tiny Sidebar (shown when closed) -->
        <summary class="group-open/sidebar:hidden flex min-h-[600px] h-full cursor-e-resize list-none flex-col items-start pl-2 pr-1.5">
          <div class="flex h-13 w-full items-center justify-start">
            <button class="cursor-e-resize flex size-10 items-center justify-center rounded-lg text-neutral-500 hover:bg-neutral-100 focus:outline-none focus-visible:bg-neutral-100 disabled:opacity-50 sm:size-9 dark:text-neutral-400 hover:text-neutral-700 dark:hover:text-neutral-100 dark:hover:bg-neutral-800 focus-visible:dark:bg-neutral-800" data-action="click->sidebar#open">
              <svg xmlns="http://www.w3.org/2000/svg" class="size-4 sm:size-4.5" width="18" height="18" viewBox="0 0 18 18"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><line x1="6.25" y1="2.75" x2="6.25" y2="15.25"></line><polyline points="10.25 6.5 12.75 9 10.25 11.5"></polyline><rect x="1.75" y="2.75" width="14.5" height="12.5" rx="2" ry="2" transform="translate(18 18) rotate(180)"></rect></g></svg>
            </button>
          </div>

          <div class="mt-1.5 sm:mt-2 flex w-full flex-col items-start">
            <a href="javascript:void(0)" aria-label="Dashboard" data-controller="tooltip" data-tooltip-content="Dashboard" data-tooltip-placement-value="right" data-tooltip-arrow="true" data-tooltip-animation-value="fade origin" data-tooltip-size-value="small" class="flex h-8 w-8.5 items-center justify-center rounded-lg text-neutral-700 hover:bg-neutral-100 focus:outline-none focus-visible:bg-neutral-100 disabled:opacity-50 dark:text-neutral-100 hover:text-neutral-700 dark:hover:text-neutral-100 dark:hover:bg-neutral-800 focus-visible:dark:bg-neutral-800">
              <svg xmlns="http://www.w3.org/2000/svg" class="size-4 sm:size-4.5" width="18" height="18" viewBox="0 0 18 18"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><path d="M14.855 5.95L9.605 1.96C9.247 1.688 8.752 1.688 8.395 1.96L3.145 5.95C2.896 6.139 2.75 6.434 2.75 6.747V14.251C2.75 15.356 3.645 16.251 4.75 16.251H7.25V12.251C7.25 11.699 7.698 11.251 8.25 11.251H9.75C10.302 11.251 10.75 11.699 10.75 12.251V16.251H13.25C14.355 16.251 15.25 15.356 15.25 14.251V6.746C15.25 6.433 15.104 6.14 14.855 5.95Z"></path></g></svg>
            </a>
            <a href="javascript:void(0)" aria-label="Contacts" data-controller="tooltip" data-tooltip-content="Contacts" data-tooltip-placement-value="right" data-tooltip-arrow="true" data-tooltip-animation-value="fade origin" data-tooltip-size-value="small" class="flex h-8 w-8.5 items-center justify-center rounded-lg text-neutral-700 hover:bg-neutral-100 focus:outline-none focus-visible:bg-neutral-100 disabled:opacity-50 dark:text-neutral-100 hover:text-neutral-700 dark:hover:text-neutral-100 dark:hover:bg-neutral-800 focus-visible:dark:bg-neutral-800">
              <svg xmlns="http://www.w3.org/2000/svg" class="size-4 sm:size-4.5" width="18" height="18" viewBox="0 0 18 18"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><circle cx="9" cy="4.5" r="2.75"></circle><path d="M13.762,15.516c.86-.271,1.312-1.221,.947-2.045-.97-2.191-3.159-3.721-5.709-3.721s-4.739,1.53-5.709,3.721c-.365,.825,.087,1.774,.947,2.045,1.225,.386,2.846,.734,4.762,.734s3.537-.348,4.762-.734Z"></path></g></svg>
            </a>
            <a href="javascript:void(0)" aria-label="Companies" data-controller="tooltip" data-tooltip-content="Companies" data-tooltip-placement-value="right" data-tooltip-arrow="true" data-tooltip-animation-value="fade origin" data-tooltip-size-value="small" class="flex h-8 w-8.5 items-center justify-center rounded-lg text-neutral-700 hover:bg-neutral-100 focus:outline-none focus-visible:bg-neutral-100 disabled:opacity-50 dark:text-neutral-100 hover:text-neutral-700 dark:hover:text-neutral-100 dark:hover:bg-neutral-800 focus-visible:dark:bg-neutral-800">
              <svg xmlns="http://www.w3.org/2000/svg" class="size-4 sm:size-4.5" width="18" height="18" viewBox="0 0 18 18"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><path d="M7.75 16.25V7.75C7.75 7.198 8.198 6.75 8.75 6.75H14.25C14.802 6.75 15.25 7.198 15.25 7.75V16.25"></path> <path d="M2.75 16.25V4.412C2.75 4.01 2.99 3.64701 3.36 3.49101L7.86 1.58802C8.519 1.30902 9.25 1.79301 9.25 2.50901V3.75001"></path> <path d="M1.75 16.25H16.25"></path> <path d="M10.25 10.25V9.75"></path> <path d="M12.75 10.25V9.75"></path> <path d="M10.25 13.25V12.75"></path> <path d="M12.75 13.25V12.75"></path></g></svg>
            </a>
            <a href="javascript:void(0)" aria-label="Deals" data-controller="tooltip" data-tooltip-content="Deals" data-tooltip-placement-value="right" data-tooltip-arrow="true" data-tooltip-animation-value="fade origin" data-tooltip-size-value="small" class="flex h-8 w-8.5 items-center justify-center rounded-lg text-neutral-700 hover:bg-neutral-100 focus:outline-none focus-visible:bg-neutral-100 disabled:opacity-50 dark:text-neutral-100 hover:text-neutral-700 dark:hover:text-neutral-100 dark:hover:bg-neutral-800 focus-visible:dark:bg-neutral-800">
              <svg xmlns="http://www.w3.org/2000/svg" class="size-4 sm:size-4.5" width="18" height="18" viewBox="0 0 18 18"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><line x1="12.345" y1="11.75" x2="15.25" y2="11.75"></line><path d="M8.779,4.67l-.231-.313c-.283-.382-.73-.608-1.206-.608h-1.458c-.388,0-.761,.151-1.041,.42l-1.867,1.8c-.07,.067-.148,.123-.232,.167"></path><path d="M2.75,11.75h1.26c.303,0,.59,.138,.78,.374l1.083,1.349c.596,.742,1.632,.962,2.478,.525l3.274-1.693c1.111-.574,1.428-2.016,.661-3.003l-1.648-2.122"></path><path d="M15.258,6.138c-.085-.044-.163-.1-.233-.168l-1.867-1.8c-.28-.269-.653-.42-1.041-.42h-1.807c-.404,0-.791,.163-1.074,.453l-2.495,2.558c-.498,.51-.493,1.326,.011,1.83h0c.447,.447,1.15,.508,1.668,.145l2.83-1.985"></path><path d="M.75,5.25H1.75c.552,0,1,.448,1,1v6c0,.552-.448,1-1,1H.75"></path><path d="M17.25,5.25h-1c-.552,0-1,.448-1,1v6c0,.552,.448,1,1,1h1"></path></g></svg>
            </a>
            <a href="javascript:void(0)" aria-label="Tasks" data-controller="tooltip" data-tooltip-content="Tasks" data-tooltip-placement-value="right" data-tooltip-arrow="true" data-tooltip-animation-value="fade origin" data-tooltip-size-value="small" class="flex h-8 w-8.5 items-center justify-center rounded-lg text-neutral-700 hover:bg-neutral-100 focus:outline-none focus-visible:bg-neutral-100 disabled:opacity-50 dark:text-neutral-100 hover:text-neutral-700 dark:hover:text-neutral-100 dark:hover:bg-neutral-800 focus-visible:dark:bg-neutral-800">
              <svg xmlns="http://www.w3.org/2000/svg" class="size-4 sm:size-4.5" width="18" height="18" viewBox="0 0 18 18"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><path d="M7.99707 11.25L9.60605 12.75L13.0031 8.25"></path> <path d="M13.75 5.25H7.25C6.145 5.25 5.25 6.145 5.25 7.25V13.75C5.25 14.855 6.145 15.75 7.25 15.75H13.75C14.855 15.75 15.75 14.855 15.75 13.75V7.25C15.75 6.145 14.855 5.25 13.75 5.25Z"></path> <path d="M12.4012 2.74996C12.0022 2.06146 11.2151 1.64841 10.38 1.77291L3.45602 2.80196C2.36402 2.96386 1.61003 3.98093 1.77203 5.07393L2.75002 11.6547"></path></g></svg>
            </a>
            <a href="javascript:void(0)" aria-label="Calendar" data-controller="tooltip" data-tooltip-content="Calendar" data-tooltip-placement-value="right" data-tooltip-arrow="true" data-tooltip-animation-value="fade origin" data-tooltip-size-value="small" class="flex h-8 w-8.5 items-center justify-center rounded-lg text-neutral-700 hover:bg-neutral-100 focus:outline-none focus-visible:bg-neutral-100 disabled:opacity-50 dark:text-neutral-100 hover:text-neutral-700 dark:hover:text-neutral-100 dark:hover:bg-neutral-800 focus-visible:dark:bg-neutral-800">
              <svg xmlns="http://www.w3.org/2000/svg" class="size-4 sm:size-4.5" width="18" height="18" viewBox="0 0 18 18"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><line x1="5.75" y1="2.75" x2="5.75" y2=".75"></line><line x1="12.25" y1="2.75" x2="12.25" y2=".75"></line><rect x="2.25" y="2.75" width="13.5" height="12.5" rx="2" ry="2"></rect><line x1="2.25" y1="6.25" x2="15.75" y2="6.25"></line></g></svg>
            </a>
          </div>

          <div class="flex-grow"></div>

          <div class="py-1 sm:py-1.5 flex items-start justify-center">
            <button class="flex mb-1 h-8 w-9 items-center justify-center rounded-lg text-neutral-700 hover:bg-neutral-100 focus:outline-none focus-visible:bg-neutral-100 disabled:opacity-50 dark:text-neutral-100 hover:text-neutral-700 dark:hover:text-neutral-100 dark:hover:bg-neutral-800 focus-visible:dark:bg-neutral-800" aria-label="Profile" title="Profile">
              <img alt="Profile image" class="size-6 rounded-full object-cover" referrerpolicy="no-referrer" src="https://images.unsplash.com/photo-1570295999919-56ceb5ecca61?w=100&h=100&fit=crop">
            </button>
          </div>
        </summary>

        <!-- Full Sidebar (shown when open) -->
        <div class="h-full w-64 overflow-x-clip overflow-y-auto bg-neutral-50 text-clip whitespace-nowrap dark:bg-neutral-900" data-sidebar-target="desktopContent">
          <!-- Shared Sidebar Content Template -->
          <template data-sidebar-target="contentTemplate">
            <nav class="min-h-[600px] relative flex h-full w-full flex-1 flex-col overflow-y-auto select-none">
            <div class="sticky top-0 z-30 bg-neutral-50 dark:bg-neutral-900">
              <div class="px-1.5 sm:px-2">
                <div class="flex h-13 items-center justify-between">
                  <a href="javascript:void(0)" aria-label="Home" class="flex size-10 items-center justify-center rounded-lg text-neutral-900 hover:bg-neutral-100 focus:outline-none focus-visible:bg-neutral-100 disabled:opacity-50 sm:size-9 dark:text-neutral-100 dark:hover:bg-neutral-800 focus-visible:dark:bg-neutral-800">
                    <svg xmlns="http://www.w3.org/2000/svg" class="size-4 sm:size-4.5" width="18" height="18" viewBox="0 0 18 18"><g fill="currentColor"><path fill-rule="evenodd" clip-rule="evenodd" d="M1 9C1 4.58168 4.58179 1 9 1C13.4182 1 17 4.58168 17 9C17 13.4183 13.4182 17 9 17C4.58179 17 1 13.4183 1 9Z" fill-opacity="0.4"></path></g></svg>
                  </a>
                  <div class="flex">
                    <button class="flex size-10 cursor-w-resize items-center justify-center rounded-lg text-neutral-500 hover:bg-neutral-100 focus:outline-none focus-visible:bg-neutral-100 disabled:opacity-50 sm:size-9 dark:text-neutral-400 hover:text-neutral-700 dark:hover:text-neutral-100 dark:hover:bg-neutral-800 focus-visible:dark:bg-neutral-800" aria-expanded="true" aria-label="Close sidebar" data-state="closed" data-action="click->sidebar#close">
                      <svg xmlns="http://www.w3.org/2000/svg" class="size-4 sm:size-4.5" width="18" height="18" viewBox="0 0 18 18"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><line x1="11.75" y1="2.75" x2="11.75" y2="15.25"></line><polyline points="7.75 6.5 5.25 9 7.75 11.5"></polyline><rect x="1.75" y="2.75" width="14.5" height="12.5" rx="2" ry="2"></rect></g></svg>
                    </button>
                  </div>
                </div>
              </div>
            </div>

            <aside class="relative bg-neutral-50 p-1.5 last:mb-5 sm:p-2 dark:bg-neutral-900">
              <a href="javascript:void(0)" aria-label="Dashboard" class="group w-full flex items-center justify-between gap-2 rounded-md px-2 py-1.5 text-left text-sm text-neutral-700 hover:bg-neutral-100 focus-visible:bg-neutral-100 focus:outline-hidden disabled:cursor-not-allowed disabled:opacity-50 dark:text-neutral-100 dark:hover:bg-neutral-700/50 dark:focus-visible:bg-neutral-700/50">
                <div class="flex min-w-0 items-center gap-1.5">
                  <div class="flex items-center justify-center group-disabled:opacity-50 group-data-disabled:opacity-50">
                    <svg xmlns="http://www.w3.org/2000/svg" class="size-4 sm:size-4.5" width="18" height="18" viewBox="0 0 18 18"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><path d="M14.855 5.95L9.605 1.96C9.247 1.688 8.752 1.688 8.395 1.96L3.145 5.95C2.896 6.139 2.75 6.434 2.75 6.747V14.251C2.75 15.356 3.645 16.251 4.75 16.251H7.25V12.251C7.25 11.699 7.698 11.251 8.25 11.251H9.75C10.302 11.251 10.75 11.699 10.75 12.251V16.251H13.25C14.355 16.251 15.25 15.356 15.25 14.251V6.746C15.25 6.433 15.104 6.14 14.855 5.95Z"></path></g></svg>
                  </div>
                  <div class="flex min-w-0 grow items-center gap-2.5 group-data-no-contents-gap:gap-0">
                    <div class="truncate">Dashboard</div>
                  </div>
                </div>
                <div class="text-neutral-500 dark:text-neutral-400">
                  <div class="hidden sm:block">
                    <div class="hidden ml-auto group-hover:flex text-xs">
                      ⌘1
                    </div>
                  </div>
                </div>
              </a>
              <a href="javascript:void(0)" aria-label="Contacts" title="Contacts" class="group w-full flex items-center justify-between gap-2 rounded-md px-2 py-1.5 text-left text-sm text-neutral-700 hover:bg-neutral-100 focus-visible:bg-neutral-100 focus:outline-hidden disabled:cursor-not-allowed disabled:opacity-50 dark:text-neutral-100 dark:hover:bg-neutral-700/50 dark:focus-visible:bg-neutral-700/50">
                <div class="flex min-w-0 items-center gap-1.5">
                  <div class="flex items-center justify-center group-disabled:opacity-50 group-data-disabled:opacity-50">
                    <svg xmlns="http://www.w3.org/2000/svg" class="size-4 sm:size-4.5" width="18" height="18" viewBox="0 0 18 18"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><circle cx="9" cy="4.5" r="2.75"></circle><path d="M13.762,15.516c.86-.271,1.312-1.221,.947-2.045-.97-2.191-3.159-3.721-5.709-3.721s-4.739,1.53-5.709,3.721c-.365,.825,.087,1.774,.947,2.045,1.225,.386,2.846,.734,4.762,.734s3.537-.348,4.762-.734Z"></path></g></svg>
                  </div>
                  <div class="flex min-w-0 grow items-center gap-2.5 group-data-no-contents-gap:gap-0">
                    <div class="truncate">Contacts</div>
                  </div>
                </div>
                <div class="text-neutral-500 dark:text-neutral-400">
                  <div class="hidden sm:block">
                    <div class="hidden ml-auto group-hover:flex text-xs">
                      ⌘2
                    </div>
                  </div>
                </div>
              </a>
              <a href="javascript:void(0)" aria-label="Companies" class="group w-full flex items-center justify-between gap-2 rounded-md px-2 py-1.5 text-left text-sm text-neutral-700 hover:bg-neutral-100 focus-visible:bg-neutral-100 focus:outline-hidden disabled:cursor-not-allowed disabled:opacity-50 dark:text-neutral-100 dark:hover:bg-neutral-700/50 dark:focus-visible:bg-neutral-700/50">
                <div class="flex min-w-0 items-center gap-1.5">
                  <div class="flex items-center justify-center group-disabled:opacity-50 group-data-disabled:opacity-50">
                    <svg xmlns="http://www.w3.org/2000/svg" class="size-4 sm:size-4.5" width="18" height="18" viewBox="0 0 18 18"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><path d="M7.75 16.25V7.75C7.75 7.198 8.198 6.75 8.75 6.75H14.25C14.802 6.75 15.25 7.198 15.25 7.75V16.25"></path> <path d="M2.75 16.25V4.412C2.75 4.01 2.99 3.64701 3.36 3.49101L7.86 1.58802C8.519 1.30902 9.25 1.79301 9.25 2.50901V3.75001"></path> <path d="M1.75 16.25H16.25"></path> <path d="M10.25 10.25V9.75"></path> <path d="M12.75 10.25V9.75"></path> <path d="M10.25 13.25V12.75"></path> <path d="M12.75 13.25V12.75"></path></g></svg>
                  </div>
                  <div class="flex min-w-0 grow items-center gap-2.5 group-data-no-contents-gap:gap-0">
                    <div class="truncate">Companies</div>
                  </div>
                </div>
                <div class="text-neutral-500 dark:text-neutral-400">
                  <div class="hidden sm:block">
                    <div class="hidden ml-auto group-hover:flex text-xs">
                      ⌘3
                    </div>
                  </div>
                </div>
              </a>
              <a href="javascript:void(0)" aria-label="Deals" class="group w-full flex items-center justify-between gap-2 rounded-md px-2 py-1.5 text-left text-sm text-neutral-700 hover:bg-neutral-100 focus-visible:bg-neutral-100 focus:outline-hidden disabled:cursor-not-allowed disabled:opacity-50 dark:text-neutral-100 dark:hover:bg-neutral-700/50 dark:focus-visible:bg-neutral-700/50">
                <div class="flex min-w-0 items-center gap-1.5">
                  <div class="flex items-center justify-center group-disabled:opacity-50 group-data-disabled:opacity-50">
                    <svg xmlns="http://www.w3.org/2000/svg" class="size-4 sm:size-4.5" width="18" height="18" viewBox="0 0 18 18"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><line x1="12.345" y1="11.75" x2="15.25" y2="11.75"></line><path d="M8.779,4.67l-.231-.313c-.283-.382-.73-.608-1.206-.608h-1.458c-.388,0-.761,.151-1.041,.42l-1.867,1.8c-.07,.067-.148,.123-.232,.167"></path><path d="M2.75,11.75h1.26c.303,0,.59,.138,.78,.374l1.083,1.349c.596,.742,1.632,.962,2.478,.525l3.274-1.693c1.111-.574,1.428-2.016,.661-3.003l-1.648-2.122"></path><path d="M15.258,6.138c-.085-.044-.163-.1-.233-.168l-1.867-1.8c-.28-.269-.653-.42-1.041-.42h-1.807c-.404,0-.791,.163-1.074,.453l-2.495,2.558c-.498,.51-.493,1.326,.011,1.83h0c.447,.447,1.15,.508,1.668,.145l2.83-1.985"></path><path d="M.75,5.25H1.75c.552,0,1,.448,1,1v6c0,.552-.448,1-1,1H.75"></path><path d="M17.25,5.25h-1c-.552,0-1,.448-1,1v6c0,.552,.448,1,1,1h1"></path></g></svg>
                  </div>
                  <div class="flex min-w-0 grow items-center gap-2.5 group-data-no-contents-gap:gap-0">
                    <div class="truncate">Deals</div>
                  </div>
                </div>
                <div class="text-neutral-500 dark:text-neutral-400">
                  <div class="hidden sm:block">
                    <div class="hidden ml-auto group-hover:flex text-xs">
                      ⌘4
                    </div>
                  </div>
                </div>
              </a>
              <a href="javascript:void(0)" aria-label="Tasks" class="group w-full flex items-center justify-between gap-2 rounded-md px-2 py-1.5 text-left text-sm text-neutral-700 hover:bg-neutral-100 focus-visible:bg-neutral-100 focus:outline-hidden disabled:cursor-not-allowed disabled:opacity-50 dark:text-neutral-100 dark:hover:bg-neutral-700/50 dark:focus-visible:bg-neutral-700/50">
                <div class="flex min-w-0 items-center gap-1.5">
                  <div class="flex items-center justify-center group-disabled:opacity-50 group-data-disabled:opacity-50">
                    <svg xmlns="http://www.w3.org/2000/svg" class="size-4 sm:size-4.5" width="18" height="18" viewBox="0 0 18 18"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><path d="M7.99707 11.25L9.60605 12.75L13.0031 8.25"></path> <path d="M13.75 5.25H7.25C6.145 5.25 5.25 6.145 5.25 7.25V13.75C5.25 14.855 6.145 15.75 7.25 15.75H13.75C14.855 15.75 15.75 14.855 15.75 13.75V7.25C15.75 6.145 14.855 5.25 13.75 5.25Z"></path> <path d="M12.4012 2.74996C12.0022 2.06146 11.2151 1.64841 10.38 1.77291L3.45602 2.80196C2.36402 2.96386 1.61003 3.98093 1.77203 5.07393L2.75002 11.6547"></path></g></svg>
                  </div>
                  <div class="flex min-w-0 grow items-center gap-2.5 group-data-no-contents-gap:gap-0">
                    <div class="truncate">Tasks</div>
                  </div>
                </div>
                <div class="text-neutral-500 dark:text-neutral-400">
                  <div class="hidden sm:block">
                    <div class="hidden ml-auto group-hover:flex text-xs">
                      ⌘5
                    </div>
                  </div>
                </div>
              </a>
              <a href="javascript:void(0)" aria-label="Calendar" class="group w-full flex items-center justify-between gap-2 rounded-md px-2 py-1.5 text-left text-sm text-neutral-700 hover:bg-neutral-100 focus-visible:bg-neutral-100 focus:outline-hidden disabled:cursor-not-allowed disabled:opacity-50 dark:text-neutral-100 dark:hover:bg-neutral-700/50 dark:focus-visible:bg-neutral-700/50">
                <div class="flex min-w-0 items-center gap-1.5">
                  <div class="flex items-center justify-center group-disabled:opacity-50 group-data-disabled:opacity-50">
                    <svg xmlns="http://www.w3.org/2000/svg" class="size-4 sm:size-4.5" width="18" height="18" viewBox="0 0 18 18"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><line x1="5.75" y1="2.75" x2="5.75" y2=".75"></line><line x1="12.25" y1="2.75" x2="12.25" y2=".75"></line><rect x="2.25" y="2.75" width="13.5" height="12.5" rx="2" ry="2"></rect><line x1="2.25" y1="6.25" x2="15.75" y2="6.25"></line></g></svg>
                  </div>
                  <div class="flex min-w-0 grow items-center gap-2.5 group-data-no-contents-gap:gap-0">
                    <div class="truncate">Calendar</div>
                  </div>
                </div>
                <div class="text-neutral-500 dark:text-neutral-400">
                  <div class="hidden sm:block">
                    <div class="hidden ml-auto group-hover:flex text-xs">
                      ⌘6
                    </div>
                  </div>
                </div>
              </a>
            </aside>

            <details class="group/options mb-4 px-1.5 sm:px-2" open>
              <summary class="flex cursor-pointer list-none items-center gap-2 rounded-md px-2 py-1.5 text-left text-sm text-neutral-700 hover:bg-neutral-100 focus-visible:bg-neutral-100 focus:outline-hidden [&::-webkit-details-marker]:hidden dark:text-neutral-100 dark:hover:bg-neutral-700/50 dark:focus-visible:bg-neutral-700/50" data-no-hover-bg="true" data-no-contents-gap="true">
                <div class="flex w-full items-center justify-start gap-1 text-neutral-500 dark:text-neutral-400">
                  <h2>Sales Pipelines</h2>
                  <svg xmlns="http://www.w3.org/2000/svg" class="size-2.5 opacity-0 transition-all duration-200 group-hover/options:opacity-100 group-open/options:rotate-90"width="12" height="12" viewBox="0 0 12 12"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><polyline points="4.25 10.25 8.5 6 4.25 1.75"></polyline></g></svg>
                </div>
              </summary>
              <div>
                <div class="w-full group relative flex items-center">
                  <a href="javascript:void(0)" class="w-full flex items-center justify-between gap-2 rounded-md pl-2 pr-9 py-1.5 text-left text-sm text-neutral-700 hover:bg-neutral-100 focus-visible:bg-neutral-100 focus:outline-hidden disabled:cursor-not-allowed disabled:opacity-50 dark:text-neutral-100 dark:hover:bg-neutral-700/50 dark:focus-visible:bg-neutral-700/50">
                    <div class="flex min-w-0 items-center gap-1.5">
                      <div class="flex min-w-0 grow items-center gap-2.5 group-data-no-contents-gap:gap-0">
                        <div class="truncate">Enterprise Sales</div>
                      </div>
                    </div>
                    <div class="mr-auto text-xs text-neutral-500 dark:text-neutral-400">12</div>
                  </a>
                  <button class="absolute right-0 hidden group-hover:flex size-8 rounded-md items-center justify-center text-neutral-500 dark:text-neutral-400 hover:bg-neutral-100 focus-visible:bg-neutral-100 focus:outline-hidden disabled:cursor-not-allowed disabled:opacity-50 dark:hover:bg-neutral-700/50 dark:focus-visible:bg-neutral-700/50">
                    <svg xmlns="http://www.w3.org/2000/svg" class="size-4" width="12" height="12" viewBox="0 0 12 12"><g fill="currentColor"><circle cx="6" cy="6" r="1" stroke-width="0"></circle><circle cx="2" cy="6" r="1" stroke-width="0"></circle><circle cx="10" cy="6" r="1" stroke-width="0"></circle></g></svg>
                  </button>
                </div>
                <div class="w-full group relative flex items-center">
                  <a href="javascript:void(0)" class="w-full flex items-center justify-between gap-2 rounded-md pl-2 pr-9 py-1.5 text-left text-sm text-neutral-700 hover:bg-neutral-100 focus-visible:bg-neutral-100 focus:outline-hidden disabled:cursor-not-allowed disabled:opacity-50 dark:text-neutral-100 dark:hover:bg-neutral-700/50 dark:focus-visible:bg-neutral-700/50">
                    <div class="flex min-w-0 items-center gap-1.5">
                      <div class="flex min-w-0 grow items-center gap-2.5 group-data-no-contents-gap:gap-0">
                        <div class="truncate">SMB Sales</div>
                      </div>
                    </div>
                    <div class="mr-auto text-xs text-neutral-500 dark:text-neutral-400">24</div>
                  </a>
                  <button class="absolute right-0 hidden group-hover:flex size-8 rounded-md items-center justify-center text-neutral-500 dark:text-neutral-400 hover:bg-neutral-100 focus-visible:bg-neutral-100 focus:outline-hidden disabled:cursor-not-allowed disabled:opacity-50 dark:hover:bg-neutral-700/50 dark:focus-visible:bg-neutral-700/50">
                    <svg xmlns="http://www.w3.org/2000/svg" class="size-4" width="12" height="12" viewBox="0 0 12 12"><g fill="currentColor"><circle cx="6" cy="6" r="1" stroke-width="0"></circle><circle cx="2" cy="6" r="1" stroke-width="0"></circle><circle cx="10" cy="6" r="1" stroke-width="0"></circle></g></svg>
                  </button>
                </div>
              </div>
            </details>

            <details class="group/options mb-4 px-1.5 sm:px-2" open>
              <summary class="flex cursor-pointer list-none items-center gap-2 rounded-md px-2 py-1.5 text-left text-sm text-neutral-700 hover:bg-neutral-100 focus-visible:bg-neutral-100 focus:outline-hidden [&::-webkit-details-marker]:hidden dark:text-neutral-100 dark:hover:bg-neutral-700/50 dark:focus-visible:bg-neutral-700/50">
                <div class="flex w-full items-center justify-start gap-1 text-neutral-500 dark:text-neutral-400">
                  <h2>Recent Deals</h2>
                  <svg xmlns="http://www.w3.org/2000/svg" class="size-2.5 opacity-0 transition-all duration-200 group-hover/options:opacity-100 group-open/options:rotate-90" width="12" height="12" viewBox="0 0 12 12"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><polyline points="4.25 10.25 8.5 6 4.25 1.75"></polyline></g></svg>
                </div>
              </summary>
              <div>

                <div class="w-full group relative flex items-center">
                  <a href="javascript:void(0)" class="w-full flex items-center justify-between gap-2 rounded-md pl-2 pr-9 py-1.5 text-left text-sm text-neutral-700 hover:bg-neutral-100 focus-visible:bg-neutral-100 focus:outline-hidden disabled:cursor-not-allowed disabled:opacity-50 dark:text-neutral-100 dark:hover:bg-neutral-700/50 dark:focus-visible:bg-neutral-700/50">
                    <div class="flex min-w-0 items-center gap-1.5">
                      <div class="flex min-w-0 grow items-center gap-2.5 group-data-no-contents-gap:gap-0">
                        <div class="truncate">Acme Corp - Enterprise Plan</div>
                      </div>
                    </div>
                    <div class="mr-auto text-xs text-neutral-500 dark:text-neutral-400">$45k</div>
                  </a>
                  <button class="absolute right-0 hidden group-hover:flex size-8 rounded-md items-center justify-center text-neutral-500 dark:text-neutral-400 hover:bg-neutral-100 focus-visible:bg-neutral-100 focus:outline-hidden disabled:cursor-not-allowed disabled:opacity-50 dark:hover:bg-neutral-700/50 dark:focus-visible:bg-neutral-700/50">
                    <svg xmlns="http://www.w3.org/2000/svg" class="size-4" width="12" height="12" viewBox="0 0 12 12"><g fill="currentColor"><circle cx="6" cy="6" r="1" stroke-width="0"></circle><circle cx="2" cy="6" r="1" stroke-width="0"></circle><circle cx="10" cy="6" r="1" stroke-width="0"></circle></g></svg>
                  </button>
                </div>

                <div class="w-full group relative flex items-center">
                  <a href="javascript:void(0)" class="w-full flex items-center justify-between gap-2 rounded-md pl-2 pr-9 py-1.5 text-left text-sm text-neutral-700 hover:bg-neutral-100 focus-visible:bg-neutral-100 focus:outline-hidden disabled:cursor-not-allowed disabled:opacity-50 dark:text-neutral-100 dark:hover:bg-neutral-700/50 dark:focus-visible:bg-neutral-700/50">
                    <div class="flex min-w-0 items-center gap-1.5">
                      <div class="flex min-w-0 grow items-center gap-2.5 group-data-no-contents-gap:gap-0">
                        <div class="truncate">TechStart Inc - Starter Package</div>
                      </div>
                    </div>
                    <div class="mr-auto text-xs text-neutral-500 dark:text-neutral-400">$12k</div>
                  </a>
                  <button class="absolute right-0 hidden group-hover:flex size-8 rounded-md items-center justify-center text-neutral-500 dark:text-neutral-400 hover:bg-neutral-100 focus-visible:bg-neutral-100 focus:outline-hidden disabled:cursor-not-allowed disabled:opacity-50 dark:hover:bg-neutral-700/50 dark:focus-visible:bg-neutral-700/50">
                    <svg xmlns="http://www.w3.org/2000/svg" class="size-4" width="12" height="12" viewBox="0 0 12 12"><g fill="currentColor"><circle cx="6" cy="6" r="1" stroke-width="0"></circle><circle cx="2" cy="6" r="1" stroke-width="0"></circle><circle cx="10" cy="6" r="1" stroke-width="0"></circle></g></svg>
                  </button>
                </div>

                <div class="w-full group relative flex items-center">
                  <a href="javascript:void(0)" class="w-full flex items-center justify-between gap-2 rounded-md pl-2 pr-9 py-1.5 text-left text-sm text-neutral-700 hover:bg-neutral-100 focus-visible:bg-neutral-100 focus:outline-hidden disabled:cursor-not-allowed disabled:opacity-50 dark:text-neutral-100 dark:hover:bg-neutral-700/50 dark:focus-visible:bg-neutral-700/50">
                    <div class="flex min-w-0 items-center gap-1.5">
                      <div class="flex min-w-0 grow items-center gap-2.5 group-data-no-contents-gap:gap-0">
                        <div class="truncate">Global Solutions - Consulting</div>
                      </div>
                    </div>
                    <div class="mr-auto text-xs text-neutral-500 dark:text-neutral-400">$28k</div>
                  </a>
                  <button class="absolute right-0 hidden group-hover:flex size-8 rounded-md items-center justify-center text-neutral-500 dark:text-neutral-400 hover:bg-neutral-100 focus-visible:bg-neutral-100 focus:outline-hidden disabled:cursor-not-allowed disabled:opacity-50 dark:hover:bg-neutral-700/50 dark:focus-visible:bg-neutral-700/50">
                    <svg xmlns="http://www.w3.org/2000/svg" class="size-4" width="12" height="12" viewBox="0 0 12 12"><g fill="currentColor"><circle cx="6" cy="6" r="1" stroke-width="0"></circle><circle cx="2" cy="6" r="1" stroke-width="0"></circle><circle cx="10" cy="6" r="1" stroke-width="0"></circle></g></svg>
                  </button>
                </div>
              </div>
            </details>

            <div class="grow"></div>

            <div class="sticky bottom-0 z-30 bg-neutral-50 p-1.5 empty:hidden sm:p-2 dark:bg-neutral-900">
              <button class="w-full flex items-center gap-2 rounded-md px-1 sm:px-1.5 py-1 sm:py-1.5 text-left text-sm text-neutral-700 hover:bg-neutral-100 focus-visible:bg-neutral-100 focus:outline-hidden disabled:cursor-not-allowed disabled:opacity-50 dark:text-neutral-100 dark:hover:bg-neutral-700/50 dark:focus-visible:bg-neutral-700/50">
                <div class="flex items-center justify-center group-disabled:opacity-50 group-data-disabled:opacity-50">
                  <div class="flex h-6 w-6 shrink-0 overflow-hidden rounded-full bg-gray-500/30">
                    <img alt="Profile image" class="h-6 w-6 shrink-0 object-cover" referrerpolicy="no-referrer" src="https://images.unsplash.com/photo-1570295999919-56ceb5ecca61?w=100&h=100&fit=crop">
                  </div>
                </div>
                <div class="min-w-0">
                  <div class="flex min-w-0 grow items-center gap-2.5 group-data-no-contents-gap:gap-0">
                    <div class="truncate text-xs">John Doe</div>
                  </div>
                </div>
              </button>
            </div>
          </nav>
          </template>
        </div>
      </details>
    </div>

    <main class="min-h-[600px] grow h-auto w-auto bg-white dark:bg-neutral-950 flex flex-col justify-center items-center relative">
      <div class="w-full absolute top-0 z-10 flex items-center md:hidden p-2">
        <button type="button" class="size-9 inline-flex items-center justify-center rounded-md focus:bg-neutral-100 focus:outline-hidden focus:ring-inset dark:focus:bg-neutral-700/50 active:opacity-50" data-action="click->sidebar#openMobile" aria-label="Open sidebar">
          <span class="sr-only">Open sidebar</span>
          <svg xmlns="http://www.w3.org/2000/svg" class="size-4 sm:size-4.5" width="18" height="18" viewBox="0 0 18 18"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><line x1="2.25" y1="9" x2="15.75" y2="9"></line><line x1="2.25" y1="3.75" x2="15.75" y2="3.75"></line><line x1="2.25" y1="14.25" x2="8.25" y2="14.25"></line></g></svg>
        </button>
      </div>
      <div class="px-6 py-4 bg-neutral-50 dark:bg-neutral-900 rounded-lg border border-neutral-200 dark:border-neutral-800">
        Your content
      </div>
    </main>
  </div>
</div>

AI Chat Sidebar

A sidebar component with an AI chat interface inspired by ChatGPT.

Your content
<!-- Note: You will likely need to replace all the min-h-[600px] with a min-h-screen -->

<div class="flex h-full w-full flex-col relative" data-controller="sidebar" data-sidebar-storage-key-value="aiChatSidebar">
  <!-- Mobile Sidebar Overlay -->
  <div class="absolute inset-0 z-50 md:hidden hidden" data-sidebar-target="mobileSidebar">
    <!-- Backdrop -->
    <div class="absolute inset-0 bg-black/50 transition-opacity duration-300" data-sidebar-target="mobileBackdrop" data-action="click->sidebar#closeMobile"></div>

    <!-- Sidebar Panel -->
    <div class="absolute inset-y-0 left-0 w-64 bg-neutral-50 dark:bg-neutral-900 shadow-xl border-r border-neutral-200 dark:border-neutral-800 transition-transform duration-300 ease-out" data-sidebar-target="mobilePanel">
      <div data-sidebar-target="sharedContent"></div>
    </div>
  </div>

  <div class="relative z-0 flex h-full w-full">
    <div class="relative flex h-full flex-row">
      <details class="w-13 open:w-64 group/sidebar relative z-20 h-full shrink-0 overflow-hidden border-r border-neutral-200 bg-neutral-50 max-md:hidden motion-safe:transition-all motion-safe:duration-300 dark:border-neutral-800 dark:bg-neutral-900" data-sidebar-target="desktopSidebar">
        <!-- Tiny Sidebar (shown when closed) -->
        <summary class="group-open/sidebar:hidden flex min-h-[600px] h-full cursor-e-resize list-none flex-col items-start pl-2 pr-1.5">
          <div class="flex h-13 w-full items-center justify-start">
            <button class="cursor-e-resize flex size-10 items-center justify-center rounded-lg text-neutral-500 hover:bg-neutral-100 focus:outline-none focus-visible:bg-neutral-100 disabled:opacity-50 sm:size-9 dark:text-neutral-400 hover:text-neutral-700 dark:hover:text-neutral-100 dark:hover:bg-neutral-800 focus-visible:dark:bg-neutral-800" data-action="click->sidebar#open">
              <svg xmlns="http://www.w3.org/2000/svg" class="size-4 sm:size-4.5" width="18" height="18" viewBox="0 0 18 18"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><line x1="6.25" y1="2.75" x2="6.25" y2="15.25"></line><polyline points="10.25 6.5 12.75 9 10.25 11.5"></polyline><rect x="1.75" y="2.75" width="14.5" height="12.5" rx="2" ry="2" transform="translate(18 18) rotate(180)"></rect></g></svg>
            </button>
          </div>

          <div class="mt-1.5 sm:mt-2 flex w-full flex-col items-start">
            <a href="javascript:void(0)" aria-label="New chat" data-controller="tooltip" data-tooltip-content="New chat" data-tooltip-placement-value="right" data-tooltip-arrow="true" data-tooltip-animation-value="fade origin" data-tooltip-size-value="small" class="flex h-8 w-8.5 items-center justify-center rounded-lg text-neutral-700 hover:bg-neutral-100 focus:outline-none focus-visible:bg-neutral-100 disabled:opacity-50 dark:text-neutral-100 hover:text-neutral-700 dark:hover:text-neutral-100 dark:hover:bg-neutral-800 focus-visible:dark:bg-neutral-800">
              <svg xmlns="http://www.w3.org/2000/svg" class="size-4 sm:size-4.5" width="18" height="18" viewBox="0 0 18 18"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><path d="M15.25 9.2422V13.25C15.25 14.355 14.355 15.25 13.25 15.25H4.75C3.645 15.25 2.75 14.355 2.75 13.25V4.75C2.75 3.645 3.645 2.75 4.75 2.75H8.75781"></path> <path d="M7.75 10.25C7.75 10.25 10.0838 10.1662 10.909 9.34101L15.784 4.46601C16.4053 3.84471 16.4053 2.83731 15.784 2.21601C15.1627 1.59471 14.1553 1.59471 13.534 2.21601L8.659 7.09101C7.8809 7.86911 7.75 10.25 7.75 10.25Z"></path></g></svg>
            </a>
            <button aria-label="Search" data-controller="tooltip" data-tooltip-content="Search" data-tooltip-placement-value="right" data-tooltip-arrow="true" data-tooltip-animation-value="fade origin" data-tooltip-size-value="small" class="flex h-8 w-8.5 items-center justify-center rounded-lg text-neutral-700 hover:bg-neutral-100 focus:outline-none focus-visible:bg-neutral-100 disabled:opacity-50 dark:text-neutral-100 hover:text-neutral-700 dark:hover:text-neutral-100 dark:hover:bg-neutral-800 focus-visible:dark:bg-neutral-800">
              <svg xmlns="http://www.w3.org/2000/svg" class="size-4 sm:size-4.5" width="18" height="18" viewBox="0 0 18 18"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><path d="M15.75 15.75L11.6386 11.6386"></path> <path d="M7.75 13.25C10.7875 13.25 13.25 10.7875 13.25 7.75C13.25 4.7125 10.7875 2.25 7.75 2.25C4.7125 2.25 2.25 4.7125 2.25 7.75C2.25 10.7875 4.7125 13.25 7.75 13.25Z"></path></g></svg>
            </button>
            <a href="javascript:void(0)" aria-label="Library" data-controller="tooltip" data-tooltip-content="Library" data-tooltip-placement-value="right" data-tooltip-arrow="true" data-tooltip-animation-value="fade origin" data-tooltip-size-value="small" class="flex h-8 w-8.5 items-center justify-center rounded-lg text-neutral-700 hover:bg-neutral-100 focus:outline-none focus-visible:bg-neutral-100 disabled:opacity-50 dark:text-neutral-100 hover:text-neutral-700 dark:hover:text-neutral-100 dark:hover:bg-neutral-800 focus-visible:dark:bg-neutral-800">
              <svg xmlns="http://www.w3.org/2000/svg" class="size-4 sm:size-4.5" width="18" height="18" viewBox="0 0 18 18"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><path d="M4,14.75l5.836-5.836c.781-.781,2.047-.781,2.828,0l3.586,3.586"></path><rect x="1.75" y="3.25" width="14.5" height="11.5" rx="2" ry="2" transform="translate(18 18) rotate(180)"></rect><circle cx="5.75" cy="7.25" r="1.25" fill="currentColor" data-stroke="none" stroke="none"></circle><line x1="9" y1="1.5" x2="9" y2="3.25"></line><line x1="5.25" y1="1.5" x2="5.25" y2="3.25"></line><line x1="12.75" y1="1.5" x2="12.75" y2="3.25"></line></g></svg>
            </a>
          </div>

          <div class="flex-grow"></div>

          <div class="py-1 sm:py-1.5 flex items-start justify-center">
            <button class="flex mb-1 h-8 w-9 items-center justify-center rounded-lg text-neutral-700 hover:bg-neutral-100 focus:outline-none focus-visible:bg-neutral-100 disabled:opacity-50 dark:text-neutral-100 hover:text-neutral-700 dark:hover:text-neutral-100 dark:hover:bg-neutral-800 focus-visible:dark:bg-neutral-800" aria-label="Profile" title="Profile">
              <img alt="Profile image" class="size-6 rounded-full object-cover" referrerpolicy="no-referrer" src="https://images.unsplash.com/photo-1570295999919-56ceb5ecca61?w=100&h=100&fit=crop">
            </button>
          </div>
        </summary>

        <!-- Full Sidebar (shown when open) -->
        <div class="h-full w-64 overflow-x-clip overflow-y-auto bg-neutral-50 text-clip whitespace-nowrap dark:bg-neutral-900" data-sidebar-target="desktopContent">
          <!-- Shared Sidebar Content Template -->
          <template data-sidebar-target="contentTemplate">
            <nav class="min-h-[600px] relative flex h-full w-full flex-1 flex-col overflow-y-auto select-none">
            <div class="sticky top-0 z-30 bg-neutral-50 dark:bg-neutral-900">
              <div class="px-1.5 sm:px-2">
                <div class="flex h-13 items-center justify-between">
                  <a href="javascript:void(0)" aria-label="Home" class="flex size-10 items-center justify-center rounded-lg text-neutral-900 hover:bg-neutral-100 focus:outline-none focus-visible:bg-neutral-100 disabled:opacity-50 sm:size-9 dark:text-neutral-100 dark:hover:bg-neutral-800 focus-visible:dark:bg-neutral-800">
                    <svg xmlns="http://www.w3.org/2000/svg" class="size-4 sm:size-4.5" width="18" height="18" viewBox="0 0 18 18"><g fill="currentColor"><path fill-rule="evenodd" clip-rule="evenodd" d="M1 9C1 4.58168 4.58179 1 9 1C13.4182 1 17 4.58168 17 9C17 13.4183 13.4182 17 9 17C4.58179 17 1 13.4183 1 9Z" fill-opacity="0.4"></path></g></svg>
                  </a>
                  <div class="flex">
                    <button class="flex size-10 cursor-w-resize items-center justify-center rounded-lg text-neutral-500 hover:bg-neutral-100 focus:outline-none focus-visible:bg-neutral-100 disabled:opacity-50 sm:size-9 dark:text-neutral-400 hover:text-neutral-700 dark:hover:text-neutral-100 dark:hover:bg-neutral-800 focus-visible:dark:bg-neutral-800" aria-expanded="true" aria-label="Close sidebar" data-state="closed" data-action="click->sidebar#close">
                      <svg xmlns="http://www.w3.org/2000/svg" class="size-4 sm:size-4.5" width="18" height="18" viewBox="0 0 18 18"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><line x1="11.75" y1="2.75" x2="11.75" y2="15.25"></line><polyline points="7.75 6.5 5.25 9 7.75 11.5"></polyline><rect x="1.75" y="2.75" width="14.5" height="12.5" rx="2" ry="2"></rect></g></svg>
                    </button>
                  </div>
                </div>
              </div>
            </div>

            <aside class="relative bg-neutral-50 p-1.5 last:mb-5 sm:p-2 dark:bg-neutral-900">
              <a href="javascript:void(0)" aria-label="Home" class="group w-full flex items-center justify-between gap-2 rounded-md px-2 py-1.5 text-left text-sm text-neutral-700 hover:bg-neutral-100 focus-visible:bg-neutral-100 focus:outline-hidden disabled:cursor-not-allowed disabled:opacity-50 dark:text-neutral-100 dark:hover:bg-neutral-700/50 dark:focus-visible:bg-neutral-700/50">
                <div class="flex min-w-0 items-center gap-1.5">
                  <div class="flex items-center justify-center group-disabled:opacity-50 group-data-disabled:opacity-50">
                    <svg xmlns="http://www.w3.org/2000/svg" class="size-4 sm:size-4.5" width="18" height="18" viewBox="0 0 18 18"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><path d="M15.25 9.2422V13.25C15.25 14.355 14.355 15.25 13.25 15.25H4.75C3.645 15.25 2.75 14.355 2.75 13.25V4.75C2.75 3.645 3.645 2.75 4.75 2.75H8.75781"></path> <path d="M7.75 10.25C7.75 10.25 10.0838 10.1662 10.909 9.34101L15.784 4.46601C16.4053 3.84471 16.4053 2.83731 15.784 2.21601C15.1627 1.59471 14.1553 1.59471 13.534 2.21601L8.659 7.09101C7.8809 7.86911 7.75 10.25 7.75 10.25Z"></path></g></svg>
                  </div>
                  <div class="flex min-w-0 grow items-center gap-2.5 group-data-no-contents-gap:gap-0">
                    <div class="truncate">New chat</div>
                  </div>
                </div>
                <div class="text-neutral-500 dark:text-neutral-400">
                  <div class="hidden sm:block">
                    <div class="hidden ml-auto group-hover:flex text-xs">
                      ⌘N
                    </div>
                  </div>
                </div>
              </a>
              <button aria-label="Search" title="Search" class="group w-full flex items-center justify-between gap-2 rounded-md px-2 py-1.5 text-left text-sm text-neutral-700 hover:bg-neutral-100 focus-visible:bg-neutral-100 focus:outline-hidden disabled:cursor-not-allowed disabled:opacity-50 dark:text-neutral-100 dark:hover:bg-neutral-700/50 dark:focus-visible:bg-neutral-700/50">
                <div class="flex min-w-0 items-center gap-1.5">
                  <div class="flex items-center justify-center group-disabled:opacity-50 group-data-disabled:opacity-50">
                    <svg xmlns="http://www.w3.org/2000/svg" class="size-4 sm:size-4.5" width="18" height="18" viewBox="0 0 18 18"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><path d="M15.75 15.75L11.6386 11.6386"></path> <path d="M7.75 13.25C10.7875 13.25 13.25 10.7875 13.25 7.75C13.25 4.7125 10.7875 2.25 7.75 2.25C4.7125 2.25 2.25 4.7125 2.25 7.75C2.25 10.7875 4.7125 13.25 7.75 13.25Z"></path></g></svg>
                  </div>
                  <div class="flex min-w-0 grow items-center gap-2.5 group-data-no-contents-gap:gap-0">
                    <div class="truncate">Search chats</div>
                  </div>
                </div>
                <div class="text-neutral-500 dark:text-neutral-400">
                  <div class="hidden sm:block">
                    <div class="hidden ml-auto group-hover:flex text-xs">
                      ⌘K
                    </div>
                  </div>
                </div>
              </button>
              <a href="javascript:void(0)" aria-label="Library" class="group w-full flex items-center justify-between gap-2 rounded-md px-2 py-1.5 text-left text-sm text-neutral-700 hover:bg-neutral-100 focus-visible:bg-neutral-100 focus:outline-hidden disabled:cursor-not-allowed disabled:opacity-50 dark:text-neutral-100 dark:hover:bg-neutral-700/50 dark:focus-visible:bg-neutral-700/50">
                <div class="flex items-center justify-center group-disabled:opacity-50 group-data-disabled:opacity-50">
                  <svg xmlns="http://www.w3.org/2000/svg" class="size-4 sm:size-4.5" width="18" height="18" viewBox="0 0 18 18"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><path d="M4,14.75l5.836-5.836c.781-.781,2.047-.781,2.828,0l3.586,3.586"></path><rect x="1.75" y="3.25" width="14.5" height="11.5" rx="2" ry="2" transform="translate(18 18) rotate(180)"></rect><circle cx="5.75" cy="7.25" r="1.25" fill="currentColor" data-stroke="none" stroke="none"></circle><line x1="9" y1="1.5" x2="9" y2="3.25"></line><line x1="5.25" y1="1.5" x2="5.25" y2="3.25"></line><line x1="12.75" y1="1.5" x2="12.75" y2="3.25"></line></g></svg>
                </div>
                <div class="flex min-w-0 grow items-center gap-2.5 group-data-no-contents-gap:gap-0">
                  <div class="truncate">Library</div>
                </div>
              </a>
            </aside>

            <details class="group/options mb-4 px-1.5 sm:px-2" open>
              <summary class="flex cursor-pointer list-none items-center gap-2 rounded-md px-2 py-1.5 text-left text-sm text-neutral-700 hover:bg-neutral-100 focus-visible:bg-neutral-100 focus:outline-hidden [&::-webkit-details-marker]:hidden dark:text-neutral-100 dark:hover:bg-neutral-700/50 dark:focus-visible:bg-neutral-700/50" data-no-hover-bg="true" data-no-contents-gap="true">
                <div class="flex w-full items-center justify-start gap-1 text-neutral-500 dark:text-neutral-400">
                  <h2>Projects</h2>
                  <svg xmlns="http://www.w3.org/2000/svg" class="size-2.5 opacity-0 transition-all duration-200 group-hover/options:opacity-100 group-open/options:rotate-90"width="12" height="12" viewBox="0 0 12 12"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><polyline points="4.25 10.25 8.5 6 4.25 1.75"></polyline></g></svg>
                </div>
              </summary>
              <div>
                <button class="group w-full flex items-center justify-between gap-2 rounded-md px-2 py-1.5 text-left text-sm text-neutral-700 hover:bg-neutral-100 focus-visible:bg-neutral-100 focus:outline-hidden disabled:cursor-not-allowed disabled:opacity-50 dark:text-neutral-100 dark:hover:bg-neutral-700/50 dark:focus-visible:bg-neutral-700/50">
                  <div class="flex items-center justify-center group-disabled:opacity-50 group-data-disabled:opacity-50">
                    <svg xmlns="http://www.w3.org/2000/svg" class="size-4 sm:size-4.5" width="18" height="18" viewBox="0 0 18 18"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><path d="M2.25 8.75V4.75C2.25 3.645 3.145 2.75 4.25 2.75H6.201C6.808 2.75 7.381 3.02499 7.761 3.49799L8.364 4.25H13.75C14.855 4.25 15.75 5.145 15.75 6.25V9.09399"></path> <path d="M14.75 12.25V17.25"></path> <path d="M15.75 9.42221V8.75C15.75 7.646 14.855 6.75 13.75 6.75H4.25C3.145 6.75 2.25 7.646 2.25 8.75V13.25C2.25 14.354 3.145 15.25 4.25 15.25H9.2919"></path> <path d="M17.25 14.75H12.25"></path></g></svg>
                  </div>
                  <div class="flex min-w-0 grow items-center gap-2.5 group-data-no-contents-gap:gap-0">
                    <div class="truncate">New project</div>
                  </div>
                </button>
                <div class="w-full group relative flex items-center">
                  <a href="javascript:void(0)" class="w-full flex items-center justify-between gap-2 rounded-md px-2 py-1.5 text-left text-sm text-neutral-700 hover:bg-neutral-100 focus-visible:bg-neutral-100 focus:outline-hidden disabled:cursor-not-allowed disabled:opacity-50 dark:text-neutral-100 dark:hover:bg-neutral-700/50 dark:focus-visible:bg-neutral-700/50">
                    <div class="flex min-w-0 items-center gap-1.5">
                      <div class="flex items-center justify-center group-disabled:opacity-50 group-data-disabled:opacity-50">
                        <svg xmlns="http://www.w3.org/2000/svg" class="size-4 sm:size-4.5" width="18" height="18" viewBox="0 0 18 18"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><path d="M13.75,5.25c1.105,0,2,.895,2,2v5.5c0,1.105-.895,2-2,2H4.25c-1.105,0-2-.895-2-2V4.75c0-1.105,.895-2,2-2h1.825c.587,0,1.144,.258,1.524,.705l1.524,1.795h4.626Z"></path></g></svg>
                      </div>
                      <div class="flex min-w-0 grow items-center gap-2.5 group-data-no-contents-gap:gap-0">
                        <div class="truncate">Rails Blocks</div>
                      </div>
                    </div>
                  </a>
                  <button class="absolute right-0 hidden group-hover:flex size-8 rounded-md items-center justify-center text-neutral-500 dark:text-neutral-400 hover:bg-neutral-100 focus-visible:bg-neutral-100 focus:outline-hidden disabled:cursor-not-allowed disabled:opacity-50 dark:hover:bg-neutral-700/50 dark:focus-visible:bg-neutral-700/50">
                    <svg xmlns="http://www.w3.org/2000/svg" class="size-4" width="12" height="12" viewBox="0 0 12 12"><g fill="currentColor"><circle cx="6" cy="6" r="1" stroke-width="0"></circle><circle cx="2" cy="6" r="1" stroke-width="0"></circle><circle cx="10" cy="6" r="1" stroke-width="0"></circle></g></svg>
                  </button>
                </div>
              </div>
            </details>

            <details class="group/options mb-4 px-1.5 sm:px-2" open>
              <summary class="flex cursor-pointer list-none items-center gap-2 rounded-md px-2 py-1.5 text-left text-sm text-neutral-700 hover:bg-neutral-100 focus-visible:bg-neutral-100 focus:outline-hidden [&::-webkit-details-marker]:hidden dark:text-neutral-100 dark:hover:bg-neutral-700/50 dark:focus-visible:bg-neutral-700/50">
                <div class="flex w-full items-center justify-start gap-1 text-neutral-500 dark:text-neutral-400">
                  <h2>Chats</h2>
                  <svg xmlns="http://www.w3.org/2000/svg" class="size-2.5 opacity-0 transition-all duration-200 group-hover/options:opacity-100 group-open/options:rotate-90" width="12" height="12" viewBox="0 0 12 12"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><polyline points="4.25 10.25 8.5 6 4.25 1.75"></polyline></g></svg>
                </div>
              </summary>
              <div>

                <div class="w-full group relative flex items-center">
                  <a href="javascript:void(0)" class="w-full flex items-center justify-between gap-2 rounded-md px-2 py-1.5 text-left text-sm text-neutral-700 hover:bg-neutral-100 focus-visible:bg-neutral-100 focus:outline-hidden disabled:cursor-not-allowed disabled:opacity-50 dark:text-neutral-100 dark:hover:bg-neutral-700/50 dark:focus-visible:bg-neutral-700/50">
                    <div class="flex min-w-0 items-center gap-1.5">
                      <div class="flex min-w-0 grow items-center gap-2.5 group-data-no-contents-gap:gap-0">
                        <div class="truncate">How to build a Rails app</div>
                      </div>
                    </div>
                  </a>
                  <button class="absolute right-0 hidden group-hover:flex size-8 rounded-md items-center justify-center text-neutral-500 dark:text-neutral-400 hover:bg-neutral-100 focus-visible:bg-neutral-100 focus:outline-hidden disabled:cursor-not-allowed disabled:opacity-50 dark:hover:bg-neutral-700/50 dark:focus-visible:bg-neutral-700/50">
                    <svg xmlns="http://www.w3.org/2000/svg" class="size-4" width="12" height="12" viewBox="0 0 12 12"><g fill="currentColor"><circle cx="6" cy="6" r="1" stroke-width="0"></circle><circle cx="2" cy="6" r="1" stroke-width="0"></circle><circle cx="10" cy="6" r="1" stroke-width="0"></circle></g></svg>
                  </button>
                </div>

                <div class="w-full group relative flex items-center">
                  <a href="javascript:void(0)" class="w-full flex items-center justify-between gap-2 rounded-md px-2 py-1.5 text-left text-sm text-neutral-700 hover:bg-neutral-100 focus-visible:bg-neutral-100 focus:outline-hidden disabled:cursor-not-allowed disabled:opacity-50 dark:text-neutral-100 dark:hover:bg-neutral-700/50 dark:focus-visible:bg-neutral-700/50">
                    <div class="flex min-w-0 items-center gap-1.5">
                      <div class="flex min-w-0 grow items-center gap-2.5 group-data-no-contents-gap:gap-0">
                        <div class="truncate">Why are gems called gems</div>
                      </div>
                    </div>
                  </a>
                  <button class="absolute right-0 hidden group-hover:flex size-8 rounded-md items-center justify-center text-neutral-500 dark:text-neutral-400 hover:bg-neutral-100 focus-visible:bg-neutral-100 focus:outline-hidden disabled:cursor-not-allowed disabled:opacity-50 dark:hover:bg-neutral-700/50 dark:focus-visible:bg-neutral-700/50">
                    <svg xmlns="http://www.w3.org/2000/svg" class="size-4" width="12" height="12" viewBox="0 0 12 12"><g fill="currentColor"><circle cx="6" cy="6" r="1" stroke-width="0"></circle><circle cx="2" cy="6" r="1" stroke-width="0"></circle><circle cx="10" cy="6" r="1" stroke-width="0"></circle></g></svg>
                  </button>
                </div>

                <div class="w-full group relative flex items-center">
                  <a href="javascript:void(0)" class="w-full flex items-center justify-between gap-2 rounded-md px-2 py-1.5 text-left text-sm text-neutral-700 hover:bg-neutral-100 focus-visible:bg-neutral-100 focus:outline-hidden disabled:cursor-not-allowed disabled:opacity-50 dark:text-neutral-100 dark:hover:bg-neutral-700/50 dark:focus-visible:bg-neutral-700/50">
                    <div class="flex min-w-0 items-center gap-1.5">
                      <div class="flex min-w-0 grow items-center gap-2.5 group-data-no-contents-gap:gap-0">
                        <div class="truncate">How to use Hotwire</div>
                      </div>
                    </div>
                  </a>
                  <button class="absolute right-0 hidden group-hover:flex size-8 rounded-md items-center justify-center text-neutral-500 dark:text-neutral-400 hover:bg-neutral-100 focus-visible:bg-neutral-100 focus:outline-hidden disabled:cursor-not-allowed disabled:opacity-50 dark:hover:bg-neutral-700/50 dark:focus-visible:bg-neutral-700/50">
                    <svg xmlns="http://www.w3.org/2000/svg" class="size-4" width="12" height="12" viewBox="0 0 12 12"><g fill="currentColor"><circle cx="6" cy="6" r="1" stroke-width="0"></circle><circle cx="2" cy="6" r="1" stroke-width="0"></circle><circle cx="10" cy="6" r="1" stroke-width="0"></circle></g></svg>
                  </button>
                </div>
              </div>
            </details>

            <div class="grow"></div>

            <div class="sticky bottom-0 z-30 bg-neutral-50 p-1.5 empty:hidden sm:p-2 dark:bg-neutral-900">
              <button class="w-full flex items-center gap-2 rounded-md px-1 sm:px-1.5 py-1 sm:py-1.5 text-left text-sm text-neutral-700 hover:bg-neutral-100 focus-visible:bg-neutral-100 focus:outline-hidden disabled:cursor-not-allowed disabled:opacity-50 dark:text-neutral-100 dark:hover:bg-neutral-700/50 dark:focus-visible:bg-neutral-700/50">
                <div class="flex items-center justify-center group-disabled:opacity-50 group-data-disabled:opacity-50">
                  <div class="flex h-6 w-6 shrink-0 overflow-hidden rounded-full bg-gray-500/30">
                    <img alt="Profile image" class="h-6 w-6 shrink-0 object-cover" referrerpolicy="no-referrer" src="https://images.unsplash.com/photo-1570295999919-56ceb5ecca61?w=100&h=100&fit=crop">
                  </div>
                </div>
                <div class="min-w-0">
                  <div class="flex min-w-0 grow items-center gap-2.5 group-data-no-contents-gap:gap-0">
                    <div class="truncate text-xs">John Doe</div>
                  </div>
                </div>
              </button>
            </div>
          </nav>
          </template>
        </div>
      </details>
    </div>

    <main class="min-h-[600px] grow h-auto w-auto bg-white dark:bg-neutral-950 flex flex-col justify-center items-center relative">
      <div class="w-full absolute top-0 z-10 flex items-center md:hidden p-2">
        <button type="button" class="size-9 inline-flex items-center justify-center rounded-md focus:bg-neutral-100 focus:outline-hidden focus:ring-inset dark:focus:bg-neutral-700/50 active:opacity-50" data-action="click->sidebar#openMobile" aria-label="Open sidebar">
          <span class="sr-only">Open sidebar</span>
          <svg xmlns="http://www.w3.org/2000/svg" class="size-4 sm:size-4.5" width="18" height="18" viewBox="0 0 18 18"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><line x1="2.25" y1="9" x2="15.75" y2="9"></line><line x1="2.25" y1="3.75" x2="15.75" y2="3.75"></line><line x1="2.25" y1="14.25" x2="8.25" y2="14.25"></line></g></svg>
        </button>
      </div>
      <div class="px-6 py-4 bg-neutral-50 dark:bg-neutral-900 rounded-lg border border-neutral-200 dark:border-neutral-800">
        Your content
      </div>
    </main>
  </div>
</div>

A sidebar component where the content is inside a rounded container.

Your content
<!-- Note: You will likely need to replace all the min-h-[600px] with a min-h-screen -->

<div class="flex h-full w-full flex-col relative" data-controller="sidebar" data-sidebar-storage-key-value="fancySidebar">
  <!-- Mobile Sidebar Overlay -->
  <div class="absolute inset-0 z-50 md:hidden hidden" data-sidebar-target="mobileSidebar">
    <!-- Backdrop -->
    <div class="absolute inset-0 bg-black/50 transition-opacity duration-300" data-sidebar-target="mobileBackdrop" data-action="click->sidebar#closeMobile"></div>

    <!-- Sidebar Panel -->
    <div class="absolute inset-y-0 left-0 w-64 bg-white dark:bg-neutral-950 shadow-xl border-r border-neutral-200 dark:border-neutral-800 transition-transform duration-300 ease-out" data-sidebar-target="mobilePanel">
      <div data-sidebar-target="sharedContent"></div>
    </div>
  </div>

  <div class="relative z-0 flex h-full w-full">
    <div class="relative flex h-full flex-row">
      <details class="w-13 open:w-64 group/sidebar relative z-20 h-full shrink-0 overflow-hidden bg-white max-md:hidden motion-safe:transition-all motion-safe:duration-300 dark:bg-neutral-950" data-sidebar-target="desktopSidebar">
        <!-- Tiny Sidebar (shown when closed) -->
        <summary class="group-open/sidebar:hidden flex min-h-[600px] h-full cursor-e-resize list-none flex-col items-start pl-2 pr-1.5">
          <div class="flex h-13 w-full items-center justify-start">
            <button class="cursor-e-resize flex size-10 items-center justify-center rounded-lg text-neutral-500 hover:bg-neutral-100 focus:outline-none focus-visible:bg-neutral-100 disabled:opacity-50 sm:size-9 dark:text-neutral-400 hover:text-neutral-700 dark:hover:text-neutral-100 dark:hover:bg-neutral-800 focus-visible:dark:bg-neutral-800" data-action="click->sidebar#open">
              <svg xmlns="http://www.w3.org/2000/svg" class="size-4 sm:size-4.5" width="18" height="18" viewBox="0 0 18 18"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><line x1="6.25" y1="2.75" x2="6.25" y2="15.25"></line><polyline points="10.25 6.5 12.75 9 10.25 11.5"></polyline><rect x="1.75" y="2.75" width="14.5" height="12.5" rx="2" ry="2" transform="translate(18 18) rotate(180)"></rect></g></svg>
            </button>
          </div>

          <div class="mt-1.5 sm:mt-2 flex w-full flex-col items-start">
            <a href="javascript:void(0)" aria-label="Dashboard" data-controller="tooltip" data-tooltip-content="Dashboard" data-tooltip-placement-value="right" data-tooltip-arrow="true" data-tooltip-animation-value="fade origin" data-tooltip-size-value="small" class="flex h-8 w-8.5 items-center justify-center rounded-lg text-neutral-700 hover:bg-neutral-100 focus:outline-none focus-visible:bg-neutral-100 disabled:opacity-50 dark:text-neutral-100 hover:text-neutral-700 dark:hover:text-neutral-100 dark:hover:bg-neutral-800 focus-visible:dark:bg-neutral-800">
              <svg xmlns="http://www.w3.org/2000/svg" class="size-4 sm:size-4.5" width="18" height="18" viewBox="0 0 18 18"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><path d="M14.855 5.95L9.605 1.96C9.247 1.688 8.752 1.688 8.395 1.96L3.145 5.95C2.896 6.139 2.75 6.434 2.75 6.747V14.251C2.75 15.356 3.645 16.251 4.75 16.251H7.25V12.251C7.25 11.699 7.698 11.251 8.25 11.251H9.75C10.302 11.251 10.75 11.699 10.75 12.251V16.251H13.25C14.355 16.251 15.25 15.356 15.25 14.251V6.746C15.25 6.433 15.104 6.14 14.855 5.95Z"></path></g></svg>
            </a>
            <a href="javascript:void(0)" aria-label="Contacts" data-controller="tooltip" data-tooltip-content="Contacts" data-tooltip-placement-value="right" data-tooltip-arrow="true" data-tooltip-animation-value="fade origin" data-tooltip-size-value="small" class="flex h-8 w-8.5 items-center justify-center rounded-lg text-neutral-700 hover:bg-neutral-100 focus:outline-none focus-visible:bg-neutral-100 disabled:opacity-50 dark:text-neutral-100 hover:text-neutral-700 dark:hover:text-neutral-100 dark:hover:bg-neutral-800 focus-visible:dark:bg-neutral-800">
              <svg xmlns="http://www.w3.org/2000/svg" class="size-4 sm:size-4.5" width="18" height="18" viewBox="0 0 18 18"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><circle cx="9" cy="4.5" r="2.75"></circle><path d="M13.762,15.516c.86-.271,1.312-1.221,.947-2.045-.97-2.191-3.159-3.721-5.709-3.721s-4.739,1.53-5.709,3.721c-.365,.825,.087,1.774,.947,2.045,1.225,.386,2.846,.734,4.762,.734s3.537-.348,4.762-.734Z"></path></g></svg>
            </a>
            <a href="javascript:void(0)" aria-label="Companies" data-controller="tooltip" data-tooltip-content="Companies" data-tooltip-placement-value="right" data-tooltip-arrow="true" data-tooltip-animation-value="fade origin" data-tooltip-size-value="small" class="flex h-8 w-8.5 items-center justify-center rounded-lg text-neutral-700 hover:bg-neutral-100 focus:outline-none focus-visible:bg-neutral-100 disabled:opacity-50 dark:text-neutral-100 hover:text-neutral-700 dark:hover:text-neutral-100 dark:hover:bg-neutral-800 focus-visible:dark:bg-neutral-800">
              <svg xmlns="http://www.w3.org/2000/svg" class="size-4 sm:size-4.5" width="18" height="18" viewBox="0 0 18 18"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><path d="M7.75 16.25V7.75C7.75 7.198 8.198 6.75 8.75 6.75H14.25C14.802 6.75 15.25 7.198 15.25 7.75V16.25"></path> <path d="M2.75 16.25V4.412C2.75 4.01 2.99 3.64701 3.36 3.49101L7.86 1.58802C8.519 1.30902 9.25 1.79301 9.25 2.50901V3.75001"></path> <path d="M1.75 16.25H16.25"></path> <path d="M10.25 10.25V9.75"></path> <path d="M12.75 10.25V9.75"></path> <path d="M10.25 13.25V12.75"></path> <path d="M12.75 13.25V12.75"></path></g></svg>
            </a>
            <a href="javascript:void(0)" aria-label="Deals" data-controller="tooltip" data-tooltip-content="Deals" data-tooltip-placement-value="right" data-tooltip-arrow="true" data-tooltip-animation-value="fade origin" data-tooltip-size-value="small" class="flex h-8 w-8.5 items-center justify-center rounded-lg text-neutral-700 hover:bg-neutral-100 focus:outline-none focus-visible:bg-neutral-100 disabled:opacity-50 dark:text-neutral-100 hover:text-neutral-700 dark:hover:text-neutral-100 dark:hover:bg-neutral-800 focus-visible:dark:bg-neutral-800">
              <svg xmlns="http://www.w3.org/2000/svg" class="size-4 sm:size-4.5" width="18" height="18" viewBox="0 0 18 18"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><line x1="12.345" y1="11.75" x2="15.25" y2="11.75"></line><path d="M8.779,4.67l-.231-.313c-.283-.382-.73-.608-1.206-.608h-1.458c-.388,0-.761,.151-1.041,.42l-1.867,1.8c-.07,.067-.148,.123-.232,.167"></path><path d="M2.75,11.75h1.26c.303,0,.59,.138,.78,.374l1.083,1.349c.596,.742,1.632,.962,2.478,.525l3.274-1.693c1.111-.574,1.428-2.016,.661-3.003l-1.648-2.122"></path><path d="M15.258,6.138c-.085-.044-.163-.1-.233-.168l-1.867-1.8c-.28-.269-.653-.42-1.041-.42h-1.807c-.404,0-.791,.163-1.074,.453l-2.495,2.558c-.498,.51-.493,1.326,.011,1.83h0c.447,.447,1.15,.508,1.668,.145l2.83-1.985"></path><path d="M.75,5.25H1.75c.552,0,1,.448,1,1v6c0,.552-.448,1-1,1H.75"></path><path d="M17.25,5.25h-1c-.552,0-1,.448-1,1v6c0,.552,.448,1,1,1h1"></path></g></svg>
            </a>
            <a href="javascript:void(0)" aria-label="Tasks" data-controller="tooltip" data-tooltip-content="Tasks" data-tooltip-placement-value="right" data-tooltip-arrow="true" data-tooltip-animation-value="fade origin" data-tooltip-size-value="small" class="flex h-8 w-8.5 items-center justify-center rounded-lg text-neutral-700 hover:bg-neutral-100 focus:outline-none focus-visible:bg-neutral-100 disabled:opacity-50 dark:text-neutral-100 hover:text-neutral-700 dark:hover:text-neutral-100 dark:hover:bg-neutral-800 focus-visible:dark:bg-neutral-800">
              <svg xmlns="http://www.w3.org/2000/svg" class="size-4 sm:size-4.5" width="18" height="18" viewBox="0 0 18 18"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><path d="M7.99707 11.25L9.60605 12.75L13.0031 8.25"></path> <path d="M13.75 5.25H7.25C6.145 5.25 5.25 6.145 5.25 7.25V13.75C5.25 14.855 6.145 15.75 7.25 15.75H13.75C14.855 15.75 15.75 14.855 15.75 13.75V7.25C15.75 6.145 14.855 5.25 13.75 5.25Z"></path> <path d="M12.4012 2.74996C12.0022 2.06146 11.2151 1.64841 10.38 1.77291L3.45602 2.80196C2.36402 2.96386 1.61003 3.98093 1.77203 5.07393L2.75002 11.6547"></path></g></svg>
            </a>
            <a href="javascript:void(0)" aria-label="Calendar" data-controller="tooltip" data-tooltip-content="Calendar" data-tooltip-placement-value="right" data-tooltip-arrow="true" data-tooltip-animation-value="fade origin" data-tooltip-size-value="small" class="flex h-8 w-8.5 items-center justify-center rounded-lg text-neutral-700 hover:bg-neutral-100 focus:outline-none focus-visible:bg-neutral-100 disabled:opacity-50 dark:text-neutral-100 hover:text-neutral-700 dark:hover:text-neutral-100 dark:hover:bg-neutral-800 focus-visible:dark:bg-neutral-800">
              <svg xmlns="http://www.w3.org/2000/svg" class="size-4 sm:size-4.5" width="18" height="18" viewBox="0 0 18 18"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><line x1="5.75" y1="2.75" x2="5.75" y2=".75"></line><line x1="12.25" y1="2.75" x2="12.25" y2=".75"></line><rect x="2.25" y="2.75" width="13.5" height="12.5" rx="2" ry="2"></rect><line x1="2.25" y1="6.25" x2="15.75" y2="6.25"></line></g></svg>
            </a>
          </div>

          <div class="flex-grow"></div>

          <div class="py-1 sm:py-1.5 flex items-start justify-center">
            <button class="flex mb-1 h-8 w-9 items-center justify-center rounded-lg text-neutral-700 hover:bg-neutral-100 focus:outline-none focus-visible:bg-neutral-100 disabled:opacity-50 dark:text-neutral-100 hover:text-neutral-700 dark:hover:text-neutral-100 dark:hover:bg-neutral-800 focus-visible:dark:bg-neutral-800" aria-label="Profile" title="Profile">
              <img alt="Profile image" class="size-6 rounded-full object-cover" referrerpolicy="no-referrer" src="https://images.unsplash.com/photo-1570295999919-56ceb5ecca61?w=100&h=100&fit=crop">
            </button>
          </div>
        </summary>

        <!-- Full Sidebar (shown when open) -->
        <div class="h-full w-64 overflow-x-clip overflow-y-auto bg-white text-clip whitespace-nowrap dark:bg-neutral-950" data-sidebar-target="desktopContent">
          <!-- Shared Sidebar Content Template -->
          <template data-sidebar-target="contentTemplate">
            <nav class="min-h-[600px] relative flex h-full w-full flex-1 flex-col overflow-y-auto select-none">
            <div class="sticky top-0 z-30 bg-white dark:bg-neutral-950">
              <div class="px-1.5 sm:px-2">
                <div class="flex h-13 items-center justify-between">
                  <a href="javascript:void(0)" aria-label="Home" class="flex size-10 items-center justify-center rounded-lg text-neutral-900 hover:bg-neutral-100 focus:outline-none focus-visible:bg-neutral-100 disabled:opacity-50 sm:size-9 dark:text-neutral-100 dark:hover:bg-neutral-800 focus-visible:dark:bg-neutral-800">
                    <svg xmlns="http://www.w3.org/2000/svg" class="size-4 sm:size-4.5" width="18" height="18" viewBox="0 0 18 18"><g fill="currentColor"><path fill-rule="evenodd" clip-rule="evenodd" d="M1 9C1 4.58168 4.58179 1 9 1C13.4182 1 17 4.58168 17 9C17 13.4183 13.4182 17 9 17C4.58179 17 1 13.4183 1 9Z" fill-opacity="0.4"></path></g></svg>
                  </a>
                  <div class="flex">
                    <button class="flex size-10 cursor-w-resize items-center justify-center rounded-lg text-neutral-500 hover:bg-neutral-100 focus:outline-none focus-visible:bg-neutral-100 disabled:opacity-50 sm:size-9 dark:text-neutral-400 hover:text-neutral-700 dark:hover:text-neutral-100 dark:hover:bg-neutral-800 focus-visible:dark:bg-neutral-800" aria-expanded="true" aria-label="Close sidebar" data-state="closed" data-action="click->sidebar#close">
                      <svg xmlns="http://www.w3.org/2000/svg" class="size-4 sm:size-4.5" width="18" height="18" viewBox="0 0 18 18"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><line x1="11.75" y1="2.75" x2="11.75" y2="15.25"></line><polyline points="7.75 6.5 5.25 9 7.75 11.5"></polyline><rect x="1.75" y="2.75" width="14.5" height="12.5" rx="2" ry="2"></rect></g></svg>
                    </button>
                  </div>
                </div>
              </div>
            </div>

            <aside class="relative p-1.5 last:mb-5 sm:p-2">
              <a href="javascript:void(0)" aria-label="Dashboard" class="group w-full flex items-center justify-between gap-2 rounded-md px-2 py-1.5 text-left text-sm text-neutral-700 hover:bg-neutral-100 focus-visible:bg-neutral-100 focus:outline-hidden disabled:cursor-not-allowed disabled:opacity-50 dark:text-neutral-100 dark:hover:bg-neutral-700/50 dark:focus-visible:bg-neutral-700/50">
                <div class="flex min-w-0 items-center gap-1.5">
                  <div class="flex items-center justify-center group-disabled:opacity-50 group-data-disabled:opacity-50">
                    <svg xmlns="http://www.w3.org/2000/svg" class="size-4 sm:size-4.5" width="18" height="18" viewBox="0 0 18 18"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><path d="M14.855 5.95L9.605 1.96C9.247 1.688 8.752 1.688 8.395 1.96L3.145 5.95C2.896 6.139 2.75 6.434 2.75 6.747V14.251C2.75 15.356 3.645 16.251 4.75 16.251H7.25V12.251C7.25 11.699 7.698 11.251 8.25 11.251H9.75C10.302 11.251 10.75 11.699 10.75 12.251V16.251H13.25C14.355 16.251 15.25 15.356 15.25 14.251V6.746C15.25 6.433 15.104 6.14 14.855 5.95Z"></path></g></svg>
                  </div>
                  <div class="flex min-w-0 grow items-center gap-2.5 group-data-no-contents-gap:gap-0">
                    <div class="truncate">Dashboard</div>
                  </div>
                </div>
                <div class="text-neutral-500 dark:text-neutral-400">
                  <div class="hidden sm:block">
                    <div class="hidden ml-auto group-hover:flex text-xs">
                      ⌘1
                    </div>
                  </div>
                </div>
              </a>
              <a href="javascript:void(0)" aria-label="Contacts" title="Contacts" class="group w-full flex items-center justify-between gap-2 rounded-md px-2 py-1.5 text-left text-sm text-neutral-700 hover:bg-neutral-100 focus-visible:bg-neutral-100 focus:outline-hidden disabled:cursor-not-allowed disabled:opacity-50 dark:text-neutral-100 dark:hover:bg-neutral-700/50 dark:focus-visible:bg-neutral-700/50">
                <div class="flex min-w-0 items-center gap-1.5">
                  <div class="flex items-center justify-center group-disabled:opacity-50 group-data-disabled:opacity-50">
                    <svg xmlns="http://www.w3.org/2000/svg" class="size-4 sm:size-4.5" width="18" height="18" viewBox="0 0 18 18"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><circle cx="9" cy="4.5" r="2.75"></circle><path d="M13.762,15.516c.86-.271,1.312-1.221,.947-2.045-.97-2.191-3.159-3.721-5.709-3.721s-4.739,1.53-5.709,3.721c-.365,.825,.087,1.774,.947,2.045,1.225,.386,2.846,.734,4.762,.734s3.537-.348,4.762-.734Z"></path></g></svg>
                  </div>
                  <div class="flex min-w-0 grow items-center gap-2.5 group-data-no-contents-gap:gap-0">
                    <div class="truncate">Contacts</div>
                  </div>
                </div>
                <div class="text-neutral-500 dark:text-neutral-400">
                  <div class="hidden sm:block">
                    <div class="hidden ml-auto group-hover:flex text-xs">
                      ⌘2
                    </div>
                  </div>
                </div>
              </a>
              <a href="javascript:void(0)" aria-label="Companies" class="group w-full flex items-center justify-between gap-2 rounded-md px-2 py-1.5 text-left text-sm text-neutral-700 hover:bg-neutral-100 focus-visible:bg-neutral-100 focus:outline-hidden disabled:cursor-not-allowed disabled:opacity-50 dark:text-neutral-100 dark:hover:bg-neutral-700/50 dark:focus-visible:bg-neutral-700/50">
                <div class="flex min-w-0 items-center gap-1.5">
                  <div class="flex items-center justify-center group-disabled:opacity-50 group-data-disabled:opacity-50">
                    <svg xmlns="http://www.w3.org/2000/svg" class="size-4 sm:size-4.5" width="18" height="18" viewBox="0 0 18 18"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><path d="M7.75 16.25V7.75C7.75 7.198 8.198 6.75 8.75 6.75H14.25C14.802 6.75 15.25 7.198 15.25 7.75V16.25"></path> <path d="M2.75 16.25V4.412C2.75 4.01 2.99 3.64701 3.36 3.49101L7.86 1.58802C8.519 1.30902 9.25 1.79301 9.25 2.50901V3.75001"></path> <path d="M1.75 16.25H16.25"></path> <path d="M10.25 10.25V9.75"></path> <path d="M12.75 10.25V9.75"></path> <path d="M10.25 13.25V12.75"></path> <path d="M12.75 13.25V12.75"></path></g></svg>
                  </div>
                  <div class="flex min-w-0 grow items-center gap-2.5 group-data-no-contents-gap:gap-0">
                    <div class="truncate">Companies</div>
                  </div>
                </div>
                <div class="text-neutral-500 dark:text-neutral-400">
                  <div class="hidden sm:block">
                    <div class="hidden ml-auto group-hover:flex text-xs">
                      ⌘3
                    </div>
                  </div>
                </div>
              </a>
              <a href="javascript:void(0)" aria-label="Deals" class="group w-full flex items-center justify-between gap-2 rounded-md px-2 py-1.5 text-left text-sm text-neutral-700 hover:bg-neutral-100 focus-visible:bg-neutral-100 focus:outline-hidden disabled:cursor-not-allowed disabled:opacity-50 dark:text-neutral-100 dark:hover:bg-neutral-700/50 dark:focus-visible:bg-neutral-700/50">
                <div class="flex min-w-0 items-center gap-1.5">
                  <div class="flex items-center justify-center group-disabled:opacity-50 group-data-disabled:opacity-50">
                    <svg xmlns="http://www.w3.org/2000/svg" class="size-4 sm:size-4.5" width="18" height="18" viewBox="0 0 18 18"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><line x1="12.345" y1="11.75" x2="15.25" y2="11.75"></line><path d="M8.779,4.67l-.231-.313c-.283-.382-.73-.608-1.206-.608h-1.458c-.388,0-.761,.151-1.041,.42l-1.867,1.8c-.07,.067-.148,.123-.232,.167"></path><path d="M2.75,11.75h1.26c.303,0,.59,.138,.78,.374l1.083,1.349c.596,.742,1.632,.962,2.478,.525l3.274-1.693c1.111-.574,1.428-2.016,.661-3.003l-1.648-2.122"></path><path d="M15.258,6.138c-.085-.044-.163-.1-.233-.168l-1.867-1.8c-.28-.269-.653-.42-1.041-.42h-1.807c-.404,0-.791,.163-1.074,.453l-2.495,2.558c-.498,.51-.493,1.326,.011,1.83h0c.447,.447,1.15,.508,1.668,.145l2.83-1.985"></path><path d="M.75,5.25H1.75c.552,0,1,.448,1,1v6c0,.552-.448,1-1,1H.75"></path><path d="M17.25,5.25h-1c-.552,0-1,.448-1,1v6c0,.552,.448,1,1,1h1"></path></g></svg>
                  </div>
                  <div class="flex min-w-0 grow items-center gap-2.5 group-data-no-contents-gap:gap-0">
                    <div class="truncate">Deals</div>
                  </div>
                </div>
                <div class="text-neutral-500 dark:text-neutral-400">
                  <div class="hidden sm:block">
                    <div class="hidden ml-auto group-hover:flex text-xs">
                      ⌘4
                    </div>
                  </div>
                </div>
              </a>
              <a href="javascript:void(0)" aria-label="Tasks" class="group w-full flex items-center justify-between gap-2 rounded-md px-2 py-1.5 text-left text-sm text-neutral-700 hover:bg-neutral-100 focus-visible:bg-neutral-100 focus:outline-hidden disabled:cursor-not-allowed disabled:opacity-50 dark:text-neutral-100 dark:hover:bg-neutral-700/50 dark:focus-visible:bg-neutral-700/50">
                <div class="flex min-w-0 items-center gap-1.5">
                  <div class="flex items-center justify-center group-disabled:opacity-50 group-data-disabled:opacity-50">
                    <svg xmlns="http://www.w3.org/2000/svg" class="size-4 sm:size-4.5" width="18" height="18" viewBox="0 0 18 18"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><path d="M7.99707 11.25L9.60605 12.75L13.0031 8.25"></path> <path d="M13.75 5.25H7.25C6.145 5.25 5.25 6.145 5.25 7.25V13.75C5.25 14.855 6.145 15.75 7.25 15.75H13.75C14.855 15.75 15.75 14.855 15.75 13.75V7.25C15.75 6.145 14.855 5.25 13.75 5.25Z"></path> <path d="M12.4012 2.74996C12.0022 2.06146 11.2151 1.64841 10.38 1.77291L3.45602 2.80196C2.36402 2.96386 1.61003 3.98093 1.77203 5.07393L2.75002 11.6547"></path></g></svg>
                  </div>
                  <div class="flex min-w-0 grow items-center gap-2.5 group-data-no-contents-gap:gap-0">
                    <div class="truncate">Tasks</div>
                  </div>
                </div>
                <div class="text-neutral-500 dark:text-neutral-400">
                  <div class="hidden sm:block">
                    <div class="hidden ml-auto group-hover:flex text-xs">
                      ⌘5
                    </div>
                  </div>
                </div>
              </a>
              <a href="javascript:void(0)" aria-label="Calendar" class="group w-full flex items-center justify-between gap-2 rounded-md px-2 py-1.5 text-left text-sm text-neutral-700 hover:bg-neutral-100 focus-visible:bg-neutral-100 focus:outline-hidden disabled:cursor-not-allowed disabled:opacity-50 dark:text-neutral-100 dark:hover:bg-neutral-700/50 dark:focus-visible:bg-neutral-700/50">
                <div class="flex min-w-0 items-center gap-1.5">
                  <div class="flex items-center justify-center group-disabled:opacity-50 group-data-disabled:opacity-50">
                    <svg xmlns="http://www.w3.org/2000/svg" class="size-4 sm:size-4.5" width="18" height="18" viewBox="0 0 18 18"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><line x1="5.75" y1="2.75" x2="5.75" y2=".75"></line><line x1="12.25" y1="2.75" x2="12.25" y2=".75"></line><rect x="2.25" y="2.75" width="13.5" height="12.5" rx="2" ry="2"></rect><line x1="2.25" y1="6.25" x2="15.75" y2="6.25"></line></g></svg>
                  </div>
                  <div class="flex min-w-0 grow items-center gap-2.5 group-data-no-contents-gap:gap-0">
                    <div class="truncate">Calendar</div>
                  </div>
                </div>
                <div class="text-neutral-500 dark:text-neutral-400">
                  <div class="hidden sm:block">
                    <div class="hidden ml-auto group-hover:flex text-xs">
                      ⌘6
                    </div>
                  </div>
                </div>
              </a>
            </aside>

            <details class="group/options mb-4 px-1.5 sm:px-2" open>
              <summary class="flex cursor-pointer list-none items-center gap-2 rounded-md px-2 py-1.5 text-left text-sm text-neutral-700 hover:bg-neutral-100 focus-visible:bg-neutral-100 focus:outline-hidden [&::-webkit-details-marker]:hidden dark:text-neutral-100 dark:hover:bg-neutral-700/50 dark:focus-visible:bg-neutral-700/50" data-no-hover-bg="true" data-no-contents-gap="true">
                <div class="flex w-full items-center justify-start gap-1 text-neutral-500 dark:text-neutral-400">
                  <h2>Sales Pipelines</h2>
                  <svg xmlns="http://www.w3.org/2000/svg" class="size-2.5 opacity-0 transition-all duration-200 group-hover/options:opacity-100 group-open/options:rotate-90"width="12" height="12" viewBox="0 0 12 12"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><polyline points="4.25 10.25 8.5 6 4.25 1.75"></polyline></g></svg>
                </div>
              </summary>
              <div>
                <div class="w-full group relative flex items-center">
                  <a href="javascript:void(0)" class="w-full flex items-center justify-between gap-2 rounded-md pl-2 pr-9 py-1.5 text-left text-sm text-neutral-700 hover:bg-neutral-100 focus-visible:bg-neutral-100 focus:outline-hidden disabled:cursor-not-allowed disabled:opacity-50 dark:text-neutral-100 dark:hover:bg-neutral-700/50 dark:focus-visible:bg-neutral-700/50">
                    <div class="flex min-w-0 items-center gap-1.5">
                      <div class="flex min-w-0 grow items-center gap-2.5 group-data-no-contents-gap:gap-0">
                        <div class="truncate">Enterprise Sales</div>
                      </div>
                    </div>
                    <div class="mr-auto text-xs text-neutral-500 dark:text-neutral-400">12</div>
                  </a>
                  <button class="absolute right-0 hidden group-hover:flex size-8 rounded-md items-center justify-center text-neutral-500 dark:text-neutral-400 hover:bg-neutral-100 focus-visible:bg-neutral-100 focus:outline-hidden disabled:cursor-not-allowed disabled:opacity-50 dark:hover:bg-neutral-700/50 dark:focus-visible:bg-neutral-700/50">
                    <svg xmlns="http://www.w3.org/2000/svg" class="size-4" width="12" height="12" viewBox="0 0 12 12"><g fill="currentColor"><circle cx="6" cy="6" r="1" stroke-width="0"></circle><circle cx="2" cy="6" r="1" stroke-width="0"></circle><circle cx="10" cy="6" r="1" stroke-width="0"></circle></g></svg>
                  </button>
                </div>
                <div class="w-full group relative flex items-center">
                  <a href="javascript:void(0)" class="w-full flex items-center justify-between gap-2 rounded-md pl-2 pr-9 py-1.5 text-left text-sm text-neutral-700 hover:bg-neutral-100 focus-visible:bg-neutral-100 focus:outline-hidden disabled:cursor-not-allowed disabled:opacity-50 dark:text-neutral-100 dark:hover:bg-neutral-700/50 dark:focus-visible:bg-neutral-700/50">
                    <div class="flex min-w-0 items-center gap-1.5">
                      <div class="flex min-w-0 grow items-center gap-2.5 group-data-no-contents-gap:gap-0">
                        <div class="truncate">SMB Sales</div>
                      </div>
                    </div>
                    <div class="mr-auto text-xs text-neutral-500 dark:text-neutral-400">24</div>
                  </a>
                  <button class="absolute right-0 hidden group-hover:flex size-8 rounded-md items-center justify-center text-neutral-500 dark:text-neutral-400 hover:bg-neutral-100 focus-visible:bg-neutral-100 focus:outline-hidden disabled:cursor-not-allowed disabled:opacity-50 dark:hover:bg-neutral-700/50 dark:focus-visible:bg-neutral-700/50">
                    <svg xmlns="http://www.w3.org/2000/svg" class="size-4" width="12" height="12" viewBox="0 0 12 12"><g fill="currentColor"><circle cx="6" cy="6" r="1" stroke-width="0"></circle><circle cx="2" cy="6" r="1" stroke-width="0"></circle><circle cx="10" cy="6" r="1" stroke-width="0"></circle></g></svg>
                  </button>
                </div>
              </div>
            </details>

            <details class="group/options mb-4 px-1.5 sm:px-2" open>
              <summary class="flex cursor-pointer list-none items-center gap-2 rounded-md px-2 py-1.5 text-left text-sm text-neutral-700 hover:bg-neutral-100 focus-visible:bg-neutral-100 focus:outline-hidden [&::-webkit-details-marker]:hidden dark:text-neutral-100 dark:hover:bg-neutral-700/50 dark:focus-visible:bg-neutral-700/50">
                <div class="flex w-full items-center justify-start gap-1 text-neutral-500 dark:text-neutral-400">
                  <h2>Recent Deals</h2>
                  <svg xmlns="http://www.w3.org/2000/svg" class="size-2.5 opacity-0 transition-all duration-200 group-hover/options:opacity-100 group-open/options:rotate-90" width="12" height="12" viewBox="0 0 12 12"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><polyline points="4.25 10.25 8.5 6 4.25 1.75"></polyline></g></svg>
                </div>
              </summary>
              <div>

                <div class="w-full group relative flex items-center">
                  <a href="javascript:void(0)" class="w-full flex items-center justify-between gap-2 rounded-md pl-2 pr-9 py-1.5 text-left text-sm text-neutral-700 hover:bg-neutral-100 focus-visible:bg-neutral-100 focus:outline-hidden disabled:cursor-not-allowed disabled:opacity-50 dark:text-neutral-100 dark:hover:bg-neutral-700/50 dark:focus-visible:bg-neutral-700/50">
                    <div class="flex min-w-0 items-center gap-1.5">
                      <div class="flex min-w-0 grow items-center gap-2.5 group-data-no-contents-gap:gap-0">
                        <div class="truncate">Acme Corp - Enterprise Plan</div>
                      </div>
                    </div>
                    <div class="mr-auto text-xs text-neutral-500 dark:text-neutral-400">$45k</div>
                  </a>
                  <button class="absolute right-0 hidden group-hover:flex size-8 rounded-md items-center justify-center text-neutral-500 dark:text-neutral-400 hover:bg-neutral-100 focus-visible:bg-neutral-100 focus:outline-hidden disabled:cursor-not-allowed disabled:opacity-50 dark:hover:bg-neutral-700/50 dark:focus-visible:bg-neutral-700/50">
                    <svg xmlns="http://www.w3.org/2000/svg" class="size-4" width="12" height="12" viewBox="0 0 12 12"><g fill="currentColor"><circle cx="6" cy="6" r="1" stroke-width="0"></circle><circle cx="2" cy="6" r="1" stroke-width="0"></circle><circle cx="10" cy="6" r="1" stroke-width="0"></circle></g></svg>
                  </button>
                </div>

                <div class="w-full group relative flex items-center">
                  <a href="javascript:void(0)" class="w-full flex items-center justify-between gap-2 rounded-md pl-2 pr-9 py-1.5 text-left text-sm text-neutral-700 hover:bg-neutral-100 focus-visible:bg-neutral-100 focus:outline-hidden disabled:cursor-not-allowed disabled:opacity-50 dark:text-neutral-100 dark:hover:bg-neutral-700/50 dark:focus-visible:bg-neutral-700/50">
                    <div class="flex min-w-0 items-center gap-1.5">
                      <div class="flex min-w-0 grow items-center gap-2.5 group-data-no-contents-gap:gap-0">
                        <div class="truncate">TechStart Inc - Starter Package</div>
                      </div>
                    </div>
                    <div class="mr-auto text-xs text-neutral-500 dark:text-neutral-400">$12k</div>
                  </a>
                  <button class="absolute right-0 hidden group-hover:flex size-8 rounded-md items-center justify-center text-neutral-500 dark:text-neutral-400 hover:bg-neutral-100 focus-visible:bg-neutral-100 focus:outline-hidden disabled:cursor-not-allowed disabled:opacity-50 dark:hover:bg-neutral-700/50 dark:focus-visible:bg-neutral-700/50">
                    <svg xmlns="http://www.w3.org/2000/svg" class="size-4" width="12" height="12" viewBox="0 0 12 12"><g fill="currentColor"><circle cx="6" cy="6" r="1" stroke-width="0"></circle><circle cx="2" cy="6" r="1" stroke-width="0"></circle><circle cx="10" cy="6" r="1" stroke-width="0"></circle></g></svg>
                  </button>
                </div>

                <div class="w-full group relative flex items-center">
                  <a href="javascript:void(0)" class="w-full flex items-center justify-between gap-2 rounded-md pl-2 pr-9 py-1.5 text-left text-sm text-neutral-700 hover:bg-neutral-100 focus-visible:bg-neutral-100 focus:outline-hidden disabled:cursor-not-allowed disabled:opacity-50 dark:text-neutral-100 dark:hover:bg-neutral-700/50 dark:focus-visible:bg-neutral-700/50">
                    <div class="flex min-w-0 items-center gap-1.5">
                      <div class="flex min-w-0 grow items-center gap-2.5 group-data-no-contents-gap:gap-0">
                        <div class="truncate">Global Solutions - Consulting</div>
                      </div>
                    </div>
                    <div class="mr-auto text-xs text-neutral-500 dark:text-neutral-400">$28k</div>
                  </a>
                  <button class="absolute right-0 hidden group-hover:flex size-8 rounded-md items-center justify-center text-neutral-500 dark:text-neutral-400 hover:bg-neutral-100 focus-visible:bg-neutral-100 focus:outline-hidden disabled:cursor-not-allowed disabled:opacity-50 dark:hover:bg-neutral-700/50 dark:focus-visible:bg-neutral-700/50">
                    <svg xmlns="http://www.w3.org/2000/svg" class="size-4" width="12" height="12" viewBox="0 0 12 12"><g fill="currentColor"><circle cx="6" cy="6" r="1" stroke-width="0"></circle><circle cx="2" cy="6" r="1" stroke-width="0"></circle><circle cx="10" cy="6" r="1" stroke-width="0"></circle></g></svg>
                  </button>
                </div>
              </div>
            </details>

            <div class="grow"></div>

            <div class="sticky bottom-0 z-30 bg-white p-1.5 empty:hidden sm:p-2 dark:bg-neutral-950">
              <button class="w-full flex items-center gap-2 rounded-md px-1 sm:px-1.5 py-1 sm:py-1.5 text-left text-sm text-neutral-700 hover:bg-neutral-100 focus-visible:bg-neutral-100 focus:outline-hidden disabled:cursor-not-allowed disabled:opacity-50 dark:text-neutral-100 dark:hover:bg-neutral-700/50 dark:focus-visible:bg-neutral-700/50">
                <div class="flex items-center justify-center group-disabled:opacity-50 group-data-disabled:opacity-50">
                  <div class="flex h-6 w-6 shrink-0 overflow-hidden rounded-full bg-gray-500/30">
                    <img alt="Profile image" class="h-6 w-6 shrink-0 object-cover" referrerpolicy="no-referrer" src="https://images.unsplash.com/photo-1570295999919-56ceb5ecca61?w=100&h=100&fit=crop">
                  </div>
                </div>
                <div class="min-w-0">
                  <div class="flex min-w-0 grow items-center gap-2.5 group-data-no-contents-gap:gap-0">
                    <div class="truncate text-xs">John Doe</div>
                  </div>
                </div>
              </button>
            </div>
          </nav>
          </template>
        </div>
      </details>
    </div>

    <main class="min-h-[600px] pl-1.5 md:pl-0 py-1.5 pr-1.5 grow h-auto w-auto bg-white dark:bg-neutral-950 flex flex-col justify-center items-center relative">
      <div class="w-full h-full flex justify-center items-center relative bg-neutral-50 dark:bg-neutral-900 rounded-lg border border-neutral-200 dark:border-neutral-800">
        <div class="w-full absolute top-0 z-10 flex items-center md:hidden p-2">
          <button type="button" class="size-9 inline-flex items-center justify-center rounded-md focus:bg-neutral-100 focus:outline-hidden focus:ring-inset dark:focus:bg-neutral-700/50 active:opacity-50" data-action="click->sidebar#openMobile" aria-label="Open sidebar">
            <span class="sr-only">Open sidebar</span>
            <svg xmlns="http://www.w3.org/2000/svg" class="size-4 sm:size-4.5" width="18" height="18" viewBox="0 0 18 18"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><line x1="2.25" y1="9" x2="15.75" y2="9"></line><line x1="2.25" y1="3.75" x2="15.75" y2="3.75"></line><line x1="2.25" y1="14.25" x2="8.25" y2="14.25"></line></g></svg>
          </button>
        </div>
        <div class="px-6 py-4">
          Your content
        </div>
      </div>
    </main>
  </div>
</div>

Configuration

The sidebar component is powered by a Stimulus controller that provides state persistence, mobile/desktop responsive behavior, and flexible configuration options.

Configuration Values

Prop Description Type Default
storageKey
The localStorage key used to persist the sidebar's open/closed state. Use unique keys for multiple sidebars on the same page. String "sidebarOpen"

💡 Multiple Sidebars: If you have multiple sidebars in your app, make sure to set a unique storageKey for each one to prevent them from sharing the same state. For example: data-sidebar-storage-key-value="leftSidebar" and data-sidebar-storage-key-value="rightSidebar".

Targets

Target Description Required
desktopSidebar
The <details> element that serves as the collapsible desktop sidebar Required
desktopContent
Container where the cloned sidebar content will be placed for desktop view Required
contentTemplate
The <template> element containing the sidebar content to be cloned for both mobile and desktop Required
mobileSidebar
The container for the mobile sidebar overlay (hidden on desktop) Required
mobilePanel
The sliding panel element for mobile sidebar Required
mobileBackdrop
The backdrop overlay that appears behind the mobile sidebar Required
sharedContent
Container where the cloned sidebar content will be placed for mobile view Required

Actions

Action Description Usage
open
Opens the desktop sidebar data-action="click->sidebar#open"
close
Closes the desktop sidebar data-action="click->sidebar#close"
toggle
Toggles the desktop sidebar between open and closed states data-action="click->sidebar#toggle"
openMobile
Opens the mobile sidebar with a slide-in animation data-action="click->sidebar#openMobile"
closeMobile
Closes the mobile sidebar with a slide-out animation data-action="click->sidebar#closeMobile"

State Persistence

The sidebar automatically saves its open/closed state to localStorage, so users' preferences are preserved across page loads and browser sessions. The state is restored when the page loads, ensuring a consistent experience.

  • Default State: The sidebar defaults to open for first-time visitors
  • Smooth Loading: Transitions are temporarily disabled on page load to prevent animation flashing
  • Persistent State: The sidebar's state is saved to localStorage whenever it's toggled

Mobile Behavior

The sidebar automatically adapts to mobile devices with a different interaction pattern:

  • Overlay Mode: On mobile, the sidebar appears as an overlay with a backdrop
  • Slide Animation: The sidebar slides in from the left with smooth transitions
  • Backdrop Dismissal: Clicking the backdrop closes the mobile sidebar
  • Separate State: Mobile sidebar state is not persisted to localStorage

Content Management

The sidebar uses a template-based approach to share content between mobile and desktop views:

  • Single Source of Truth: Define your sidebar content once in a <template> element
  • Automatic Cloning: Content is automatically cloned to both mobile and desktop containers on connect
  • Mobile Adaptations: The controller automatically updates close buttons in the mobile view to use the appropriate action and icon

Table of contents

Get notified when new components come out