Hooks
Nine shared hooks that Yugen UI's own components use: state, screen, storage, shortcuts and measuring.
Import
Install from npm, copy the source into your project with @yugen-ui/cli or the shadcn CLI, or import directly when working in this repo.
// npm install yugen-uiimport { useControllableState, useDisclosure, useMediaQuery, useLocalStorage, useClipboard, useHotkeys, useDebouncedValue, useIntersection, useElementSize,} from "yugen-ui";Variants
Each tile is a standalone example: hover to copy, or press Code to see the source.
The panel is closed.
useDisclosure
Open / close / toggle for dialogs, menus and panels. The functions have stable identity, so pass them straight to onClick.
useMediaQuery
Follows a CSS media query. Resize the window or switch the OS color scheme to see it update at once, without a flash on re-render.
useLocalStorage
Stores JSON in localStorage, synced between hooks with the same key and between tabs. The server and the first hydration render use the initial value, so the HTML never mismatches.
yugen_live_4f9a2c71useClipboard
Copies text to the clipboard; copied turns itself off after the timeout. A fallback for insecure contexts is built in.
Click this box, then press Ctrl / ⌘ + K, ? or an arrow key. In the input below, plain keys are ignored and Ctrl / ⌘ shortcuts still run.
useHotkeys
Keys are key combinations; mod is ⌘ on Mac and Ctrl elsewhere. Plain shortcuts don't fire while typing in a field; start / end flip in RTL.
Typing: “”
After 400ms: “”, “API” called 0 times
useDebouncedValue
The value only changes once the user stops typing for the delay. Call your API with the debounced value instead of on every key.
useIntersection
Tells you whether an element is in view. Use once for lazy loading or an effect that should run a single time.
0 × 0 px
Compact layout. Drag the bottom-right corner to resize.
useElementSize
Measures an element with ResizeObserver, so layout can follow its container instead of the window size.
Usage
- Use useControllableState in every component with an x / defaultX / onXChange pair, so controlled and uncontrolled behave the same.
- Use useMediaQuery for logic (swapping a component, changing the column count); use Tailwind breakpoints for purely visual changes.
- Prefix useLocalStorage keys with your app's name, and store only small, non-sensitive data.
- Register global shortcuts in a few high-level places, and let useHotkeys skip form fields instead of filtering yourself.
- Don't read a useLocalStorage value while rendering to decide the server HTML. The first render is always the initial value.
- Don't put tokens or personal data in localStorage.
- Don't use
left/rightfor reading-direction navigation shortcuts. Usestart/endso RTL works. - Don't debounce a value people need to see respond immediately (the input still binds the raw value).
Props
| Prop · Type | Default | Description |
|---|---|---|
| useControllableState | Default: — | The setter has stable identity and does nothing when the value is unchanged. value === undefined means uncontrolled. |
| useDisclosure | Default: defaultOpen: false | Open / closed state; also works in controlled mode. |
| useMediaQuery | Default: false | The value when there is no window (server, tests). Built on useSyncExternalStore, so it doesn't flash. |
| useLocalStorage | Default: — | set takes a value or an updater; remove() deletes the key and returns to the initial value. Corrupt data or blocked localStorage never throws. Values are stored as JSON; serialize / deserialize read an existing key in another format. |
| useClipboard | Default: 1600 | timeout (ms) is how long copied stays true. |
| useHotkeys | Default: — | options: target (document by default), enabled (true), enableOnFormTags (false), preventDefault (true), repeat (false: a held key counts once). Valid keys: mod / ctrl / meta / alt / shift + a key, plus the aliases esc, space, up, down, left, right, start, end. |
| useDebouncedValue | Default: 300 | Returns the value at once on the first render; afterwards it only changes once value has been still for delay ms. |
| useIntersection | Default: — | Attach ref to the element. Without IntersectionObserver it is always false. |
| useElementSize | Default: box: "content-box", enabled: true | 0 × 0 until measured (server, first render). The first measure runs before the browser paints, so nothing flashes. Also works with vertical writing modes. Pass enabled: false when the size is already known (a fixed width, for example): no measuring, no ResizeObserver, no extra render, and the result stays 0 × 0. |