Skip to content
fayeuiStatic / 0.1.0
LLMs
Advanced / Static edition

Browser and interaction utilities

Explicit client-only behavior, debouncing, media queries and outside-pointer handling.

Prefer declarative layout

Use CSS breakpoints and container queries for layout. useMediaQuery is for genuinely behavioral decisions and accepts a stable server snapshot. It must not create incompatible server and hydration trees.

ClientOnly is an explicit browser-only boundary. Do not wrap every troublesome component in it and call the system SSR-compatible.

State and lifecycle

useControllableState forbids switching between controlled and uncontrolled operation. useDisclosure is local UI state. useDebouncedValue validates its delay and cancels outdated timers.

ClickAwayListener is a structural wrapper with an explicit outside-pointer policy. It is not a competing dismissal implementation for Base UI menus and dialogs.

Live examples

Try the controls below, then read or copy the complete example to adapt it for your application.

Hydration-safe browser access

Browser-only content pending.

Wide viewport: false

Local interaction helpers

Debounced value: empty
Read the complete example
'use client';
import { useState } from 'react';
import { ClientOnly } from '@fayegram-ai/faye-ui/hooks/client-only';
import { useMediaQuery, useDebouncedValue, useDisclosure } from '@fayegram-ai/faye-ui/hooks';
import { Button } from '@fayegram-ai/faye-ui/controls/button';
import { Input } from '@fayegram-ai/faye-ui/controls/input';
import { Section, Result } from '../example';
export default function Utilities() {
    const [query, setQuery] = useState(''), debounced = useDebouncedValue(query, 200), wide = useMediaQuery('(min-width: 60rem)', false), disclosure = useDisclosure();
    return <><Section title="Hydration-safe browser access"><ClientOnly fallback={<p>Browser-only content pending.</p>}><p>Client-only content mounted after hydration.</p></ClientOnly><Result>Wide viewport: {String(wide)}</Result></Section><Section title="Local interaction helpers"><Input aria-label="Debounced query" value={query} onChange={e => setQuery(e.target.value)}/><Result>Debounced value: {debounced || 'empty'}</Result><Button aria-expanded={disclosure.open} onClick={disclosure.toggle}>Toggle disclosure state</Button>{disclosure.open && <p>Open state is application-owned.</p>}</Section></>;
}