---
title: Responsive and interaction hooks
description: Synchronize breakpoints when behavior must follow the viewport and add ripple feedback to custom controls.
order: 20
toc:
- id: breakpoint-contract
title: Breakpoint contract
- id: reading-breakpoints
title: Read breakpoints
- id: root-classes
title: Root classes
- id: server-rendering
title: Server rendering
- id: ripple
title: Ripple interaction
---
## Breakpoint contract {#breakpoint-contract}
The shared breakpoint splits the viewport into three ranges:
| Value | Range |
| --------- | -------------------- |
| `mobile` | Below 768px |
| `tablet` | 768px through 1023px |
| `desktop` | 1024px and above |
Prefer CSS for visual changes. Read a breakpoint during rendering only when mounted content, interaction behavior, or data volume must change.
## Read breakpoints {#reading-breakpoints}
```tsx
import {
useBreakpoint,
useIsDesktop,
useIsMobile,
useIsTablet,
useOnBreakpoint,
} from "@workspace/ui/hooks/use-breakpoint"
const breakpoint = useBreakpoint()
const isMobile = useIsMobile()
```
Every hook subscribes to the same pair of `matchMedia` queries through `useSyncExternalStore`. `useOnBreakpoint(listener)` runs only after the breakpoint changes; it does not report the initial value as a change.
## Root classes {#root-classes}
After mounting `BreakpointReactor`, the document root has exactly one of `.ui:mobile`, `.ui:tablet`, or `.ui:desktop`:
```tsx
import { BreakpointReactor } from "@workspace/ui/hooks/use-breakpoint"
;<>
>
```
`globals.css` registers these as the custom `mobile:`, `tablet:`, and `desktop:` variants, allowing CSS and behavioral code to share the same three-range definition.
## Server rendering {#server-rendering}
The server snapshot is fixed to `desktop` so SSR remains deterministic. To write the real class before hydration, place the result of `getBreakpointInitializationScript()` in an inline script in the document head:
```tsx
import { getBreakpointInitializationScript } from "@workspace/ui/hooks/use-breakpoint"
;
```
Pass the application's nonce under a strict CSP. The script only synchronizes the initial class; it does not replace `BreakpointReactor` for later viewport changes.
## Ripple interaction {#ripple}
`Button` already uses `useRippleRef`, so ordinary buttons need no extra setup. Use the hook directly for a custom interactive surface:
```tsx
import { useRipple } from "@workspace/ui/hooks/use-ripple"
export function Tile() {
const ref = useRipple({
duration: 500,
opacity: 0.2,
rippleClassName: "bg-accent",
})
return (
)
}
```
The element must establish a positioned containing block, normally with `relative`. The hook ignores disabled controls, `aria-disabled="true"`, and non-primary mouse buttons, and removes listeners and animation nodes when the ref changes or unmounts. Use `useRippleRef(ref, options)` when merging an external ref.