# Hydration-safe Inputs

**MUST** · **ID:** `interactions-hydration-safe` · **Category:** interactions
**Source:** [Vercel](https://github.com/vercel-labs/agent-skills/blob/main/skills/web-design-guidelines/SKILL.md)
**Interactive version:** https://ui-guides-agent-rules.netlify.app/principles/interactions-hydration-safe

> MUST: Hydration-safe inputs (no lost focus/value)

Inputs must not lose focus or value after hydration

In React and other frameworks with hydration, ensure input values and focus states persist from server-rendered HTML through client-side hydration. Losing focus or clearing values frustrates users who have already started typing.

## Bad — do not do this

`interactions-hydration-safe-bad`

```tsx
import { useState, useEffect } from 'react';

export function HydrationSafeBad() {
  const [mounted, setMounted] = useState(false);

  useEffect(() => {
    // Simulating hydration delay
    const timer = setTimeout(() => setMounted(true), 1000);
    return () => clearTimeout(timer);
  }, []);

  return (
    <div className="w-full max-w-sm">
      <div className="bg-card border border-border rounded-lg p-4">
        <label className="block text-sm font-medium text-foreground mb-2">
          Search
        </label>
        {mounted ? (
          <input
            type="text"
            placeholder="Start typing..."
            className="w-full px-3 py-2 border border-border rounded-lg text-base focus:outline-hidden focus:ring-2 focus:ring-ring"
          />
        ) : (
          <div className="w-full px-3 py-2 border border-border rounded-lg bg-muted text-muted-foreground">
            Loading...
          </div>
        )}
        <p className="mt-2 text-xs text-muted-foreground">
          Input is replaced after hydration, losing any typed content and focus.
        </p>
      </div>
      <p className="text-xs text-error mt-4">
        Input replaced on hydration - focus and value lost
      </p>
    </div>
  );
}
```

## Good — do this

`interactions-hydration-safe-good`

```tsx
import { useState, useEffect, useRef } from 'react';

export function HydrationSafeGood() {
  const [mounted, setMounted] = useState(false);
  const inputRef = useRef<HTMLInputElement>(null);
  const [value, setValue] = useState('');

  useEffect(() => {
    // Simulating hydration - input stays the same, just becomes interactive
    const timer = setTimeout(() => setMounted(true), 1000);
    return () => clearTimeout(timer);
  }, []);

  return (
    <div className="w-full max-w-sm">
      <div className="bg-card border border-border rounded-lg p-4">
        <label className="block text-sm font-medium text-foreground mb-2">
          Search
        </label>
        <input
          ref={inputRef}
          type="text"
          value={value}
          onChange={(e) => setValue(e.target.value)}
          placeholder="Start typing..."
          className="w-full px-3 py-2 border border-border rounded-lg text-base focus:outline-hidden focus:ring-2 focus:ring-ring"
        />
        <p className="mt-2 text-xs text-muted-foreground">
          {mounted
            ? 'Hydrated! Input preserved focus and value during hydration.'
            : 'Hydrating... Input stays interactive.'}
        </p>
      </div>
      <p className="text-xs text-success mt-4">
        Same input element persists - focus and value preserved
      </p>
    </div>
  );
}
```

## References

- [React Hydration](https://react.dev/reference/react-dom/client/hydrateRoot)
