Hydration

Attaching client behaviour to server-rendered HTML.

Stage:

JS: not yet attached

JS: event listeners attached

html

<div class="hyd hyd--server-html" role="status">
  <p class="hyd__stage">Stage: <strong class="hyd__stage-name"></strong></p>
  <button type="button" class="hyd__btn" style="--a:#A8E6C1;">Add to cart</button>
  <p class="hyd__badge hyd__badge--server-html">JS: not yet attached</p><p class="hyd__badge hyd__badge--hydrated">JS: event listeners attached</p>
</div>
<style>
.hyd { font: 13px/1.5 system-ui, sans-serif; color: #241430; background: #FAF7F2; padding: 18px; border-radius: 12px; width: 280px; box-sizing: border-box; }
.hyd__stage { margin: 0 0 12px; font-size: 12px; }
.hyd__stage-name::before { content: "server HTML (static)"; }
.hyd--hydrated .hyd__stage-name::before { content: "hydrated (interactive)"; }
.hyd__btn { border: none; padding: 10px 18px; border-radius: 8px; font: 600 13px system-ui, sans-serif; color: #241430; width: 100%; background: #24143020; cursor: default; opacity: .6; }
.hyd--hydrated .hyd__btn { background: var(--a); cursor: pointer; opacity: 1; }
.hyd__badge { display: none; margin: 10px 0 0; font-size: 10px; color: #24143099; }
.hyd--server-html .hyd__badge--server-html, .hyd--hydrated .hyd__badge--hydrated { display: block; }
</style>

tailwind

<div class="hydtw hydtw--server-html font-sans text-[#241430] bg-[#FAF7F2] p-4.5 rounded-xl w-[280px] box-border" role="status">
  <p class="m-0 mb-3 text-xs">Stage: <strong class="hydtw__stage-name"></strong></p>
  <button type="button" class="hydtw__btn border-none py-2.5 px-4.5 rounded-lg font-semibold text-[13px] text-[#241430] w-full bg-[#24143020] opacity-60" style="--a:#A8E6C1;">Add to cart</button>
  <p class="hydtw__badge hydtw__badge--server-html hidden mt-2.5 m-0 text-[10px] text-[#24143099]">JS: not yet attached</p><p class="hydtw__badge hydtw__badge--hydrated hidden mt-2.5 m-0 text-[10px] text-[#24143099]">JS: event listeners attached</p>
</div>
<style>
.hydtw__stage-name::before { content: "server HTML (static)"; }
.hydtw--hydrated .hydtw__stage-name::before { content: "hydrated (interactive)"; }
.hydtw--hydrated .hydtw__btn { background: var(--a); opacity: 1; cursor: pointer; }
.hydtw--server-html .hydtw__badge--server-html, .hydtw--hydrated .hydtw__badge--hydrated { display: block; }
</style>

react

export function Hydration({
  stage = "server-html",
  buttonLabel = "Add to cart",
  accent = "#A8E6C1",
  showJsBadge = true,
}) {
  const hydrated = stage === "hydrated";
  return (
    <div role="status" style={{ font: "13px/1.5 system-ui, sans-serif", color: "#241430", background: "#FAF7F2", padding: 18, borderRadius: 12, width: 280, boxSizing: "border-box" }}>
      <p style={{ margin: "0 0 12px", fontSize: 12 }}>Stage: <strong>{hydrated ? "hydrated (interactive)" : "server HTML (static)"}</strong></p>
      <button type="button" style={{ border: "none", padding: "10px 18px", borderRadius: 8, fontWeight: 600, fontSize: 13, color: "#241430", width: "100%", background: hydrated ? accent : "#24143020", cursor: hydrated ? "pointer" : "default", opacity: hydrated ? 1 : .6 }}>{buttonLabel}</button>
      {showJsBadge && <p style={{ margin: "10px 0 0", fontSize: 10, color: "#24143099" }}>{hydrated ? "JS: event listeners attached" : "JS: not yet attached"}</p>}
    </div>
  );
}