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.
<!-- 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.
<!-- 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>
Sidebar and content with a rounded container
A sidebar component where the content is inside a rounded container.
<!-- 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