Getting Started
App Providerglobal.d.tsThemesTypes
cn(...args)Text ParserUnits Converters
AnchorAvatarBreadcrumbBurgerButtonCardCarouselCheckerCodeColor PickerCommandConfettiCopyButtonDouble Helix WordsFloating IndicatorGroupHighlight TextIndicatorInputKbdLabelLoaderPaginationPassword RequirementPolymorphic SlotProgressProseRatingRunning AreaScroll AreaSheetsSkeletonSliderStackSvgTableTabsTextareaTimelineTimesToasterTooltipTyping WordsTypography
useClickOutsideuseClipboarduseDeviceInfouseDialoguseDidUpdateuseDirectionuseDisclosureuseDocumentTitleuseDocumentVisibilityuseElementInfouseEyeDropperuseFetchuseFullscreenuseGeoLocationuseHotkeysuseHoveruseIduseImagePopupuseInputStateuseIntersectionuseIntervaluseIsomorphicEffectuseListStateuseLocalStorageuseMeasureScrollbaruseMediaQueryuseMergedRefuseMouseuseMoveuseMutationObserveruseNetworkuseOpenStateuseOrientationuseOSusePaginationusePWAInstalleruseRandomColorsuseReducedMotionuseReloaduseResizeObserveruseScrollIntoViewuseStateHistoryuseTimeoutuseTouchuseTriggeruseUncontrolleduseValidatedStateuseViewportSizeuseWindowEventuseWindowScroll
Docs
Web
Hooks
useOpenState
useNetwork

Tracks the user's network connection and status.

useOrientation

Tracks screen orientation in real-time.


Edit this page on GitHub
  • Started
  • Utilities
  • Configuration
  • Components
  • Hooks
  • Examples
  • Github
  • Contributing
⌘+J

© 2025 oeri rights MIT


Designed in Earth-616

Built by oeri

useOpenState

Manages a toggleable state with optional external control via props.

Usage

The useOpenState hook provides an easy way to manage open/close state for components, modals, or dropdowns. It's perfect for handling visibility toggles or managing whether an element is expanded or collapsed. Below is a basic example that demonstrates how to use the useOpenState hook to manage the open/close state of a component.

"use client";
import { XIcon } from "@/icons/*";
import { cvx, cvxVariants } from "xuxi";
import { Button, UnstyledButton } from "@/ui/button";
import { useOpenState } from "@/hooks/use-open-state";
import { Stack } from "@/ui/stack";
import { Group } from "@/ui/group";
import { Kbd } from "@/ui/kbd";

export function UseOpenStateDemo() {
  const state = useOpenState({ modal: true, hotKeys: "ctrl+D" });

  return (
    <>
      <Button ref={state.triggerRef} onClick={state.toggle} className="m-auto h-auto">
        <Group align="center" gap="4">
          Dialog |
          <Kbd items={["ctrl", "D"]} />
        </Group>
      </Button>

      <state.Portal render={state.render}>
        <div onClick={state.toggle} {...state.attr()} {...classes("overlay")} />
        <div role="dialog" ref={state.contentRef as React.RefObject<HTMLDivElement>} {...state.attr()} {...classes("content")}>
          <Stack align="center" className="size-full">
            Click overlay to close
            <br />
            <Group align="center" gap="4">
              or use <Kbd items={["ctrl", "D"]} />
            </Group>
          </Stack>
          <UnstyledButton onClick={state.toggle} {...classes("close")}>
            <XIcon />
            <span className="sr-only">Close Trigger</span>
          </UnstyledButton>
        </div>
      </state.Portal>
    </>
  );
}

function classes(selector: NonNullable<cvxVariants<typeof dialog>["selector"]>) {
  return { className: dialog({ selector }) };
}
const dialog = cvx({
  variants: {
    selector: {
      overlay:
        "fixed inset-0 z-[100] bg-black/50 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-200 data-[state=open]:fade-in-0 data-[state=closed]:fade-out-0",
      content:
        "fixed left-[50%] top-[50%] z-[111] w-80 h-80 translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-300 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-200 data-[state=open]:fade-in-0 data-[state=closed]:fade-out-0 data-[state=open]:zoom-in-100 data-[state=closed]:zoom-out-100 data-[state=open]:slide-in-from-left-1/2 data-[state=closed]:slide-out-to-left-1/2 data-[state=open]:slide-in-from-top-[60%] data-[state=closed]:slide-out-to-top-[60%] rounded-lg",
      close: "size-4 absolute right-4 top-4 text-muted-foreground hover:text-color rounded-sm disabled:opacity-50"
    }
  }
});

Create Tooltip

The useOpenState hook can be used for managing the visibility of UI elements like tooltips. This section demonstrates how to create a tooltip component that opens and closes based on state managed by the hook.

"use client";
import { cvx } from "xuxi";
import { Button } from "@/ui/button";
import { useOpenState } from "@/hooks/use-open-state";

type DataSide = "top" | "right" | "bottom" | "left";
type DataAlign = "start" | "center" | "end";

export function UseOpenStateTooltipDemo({ side }: { side?: DataSide; align?: DataAlign }) {
  const state = useOpenState({
    trigger: "hover",
    sideOffset: 8,
    side: side as DataSide,
    observe: {
      touch: true,
      align: true,
      side: true,
      sideswipe: true,
      offset: true,
      contentRect: true
    }
  });

  return (
    <>
      <Button
        ref={state.triggerRef}
        {...state.attr()}
        onMouseEnter={state.handleOnMouseEnter}
        onMouseLeave={state.handleOnMouseLeave}
        onMouseMove={state.handleOnMouseMove}
        onTouchStart={state.handleOnTouchStart}
      >
        Tooltip
      </Button>

      <state.Portal render={state.render}>
        <p role="tooltip" ref={state.contentRef} className={tooltip({ side })} {...{ ...state.attr(), style: state.styleVars("content") }}>
          allows use as a tooltip
        </p>
      </state.Portal>
    </>
  );
}

const tooltip = cvx({
  assign:
    "group absolute min-w-max z-20 text-[13px] rounded-md border bg-background text-popover-foreground shadow-md outline-none focus-visible:ring-0 flex items-center justify-center py-1 px-2 w-max max-w-max transition-opacity [transition-duration:200ms] data-[state=open]:animate-in data-[state=closed]:duration-200 data-[state=open]:fade-in-0 data-[state=closed]:fade-out-0 data-[state=open]:zoom-in-100 data-[state=closed]:zoom-out-95 top-[--top] left-[--left]",
  variants: {
    side: {
      top: "data-[side=top]:slide-in-from-bottom-0 data-[side=top]:data-[state=closed]:slide-out-to-bottom-0",
      right: "data-[side=right]:slide-in-from-left-0 data-[side=right]:data-[state=closed]:slide-out-to-left-0",
      bottom: "data-[side=bottom]:slide-in-from-top-0 data-[side=bottom]:data-[state=closed]:slide-out-to-top-0",
      left: "data-[side=left]:slide-in-from-right-0 data-[side=left]:data-[state=closed]:slide-out-to-right-0"
    }
  }
});

Depend on

  • useHotkeys
  • useMeasureScrollbar

Source Codes

Full working code example, including necessary markup and styles. You can copy and paste this code directly to start using the component immediately.

use-open-state.ts