# HP1007: Different element rendered on server and client

> HP1007 (element mismatch): the server rendered one element and the client another in the same place, so React threw the server HTML away. Causes and fixes.

Source: https://hydration.jscrate.dev/docs/issues/hp1007
Last updated: 2026-09-18

HP1007 (`element-mismatch`) means the server rendered one element and React
rendered a different one in the same place, such as a `<ul>` on the server and a
`<select>` in the browser. React cannot reuse the server's element, so it
discards that part of the page and renders it again. Render the same element
type on both sides.

| | |
| --- | --- |
| Code | `HP1007` |
| Name | `element-mismatch` |
| Default severity | Error |
| Group | DOM mismatches |
| What it means | The server rendered one element and the client rendered a different one in the same place. |

## What HP1007 (`element-mismatch`) means

hydration-proof compares the DOM right before and inside the hydration commit,
and every node keeps its identity, so it sees exactly which element React
replaced and with what. The finding shows the two tags, for example
`server: "<ul>"` and `client: "<select>"`, on the element's position in the
page.

A tag change is structural: React throws the server HTML away up to the
nearest Suspense boundary, which is slower and resets state in that part of
the page. When React re-rendered a branch around the element, the finding's
evidence names that branch.

When invalid HTML is the reason the elements differ (the browser moved a
`<div>` out of a `<p>`), the difference is reported as
[HP3001](https://hydration.jscrate.dev/docs/issues/hp3001) instead, with this one folded into it.

## The React error it matches

```text
Hydration failed because the server rendered HTML didn't match the client. As a result this tree will be regenerated on the client.
Warning: Expected server HTML to contain a matching <div> in <div>
```

The first is React 19, the second the React 18 development warning. See
[the server rendered HTML didn't match the client](https://hydration.jscrate.dev/docs/errors/hydration-failed-server-rendered-html-didnt-match-client)
and [Expected server HTML to contain a matching](https://hydration.jscrate.dev/docs/errors/expected-server-html-to-contain-a-matching).

## Likely causes

- A layout chosen with `matchMedia` or the window size during render: a
  [media query](https://hydration.jscrate.dev/docs/causes/media-query).
- A branch on `typeof window`, the user agent or another
  [browser-only API](https://hydration.jscrate.dev/docs/causes/browser-api), such as a link on one side and
  a `<span>` on the other.
- A flag from [browser storage](https://hydration.jscrate.dev/docs/causes/storage) or data the client
  fetched again: [different data](https://hydration.jscrate.dev/docs/causes/data).

## How to fix it

- Render the same element type on server and client; branch on data that both
  sides share.

A menu that picks its element from the screen size:

```tsx title="nav-menu.tsx"
"use client";

export function NavMenu() {
  const narrow =
    typeof window !== "undefined" &&
    window.matchMedia("(max-width: 600px)").matches;

  return narrow ? (
    <select aria-label="Menu">
      <option>Docs</option>
    </select>
  ) : (
    <ul>
      <li>Docs</li>
    </ul>
  );
}
```

Render both and let CSS show one, so the markup is the same everywhere:

```tsx title="nav-menu.tsx"
export function NavMenu() {
  return (
    <>
      <select className="nav-narrow" aria-label="Menu">
        <option>Docs</option>
      </select>
      <ul className="nav-wide">
        <li>Docs</li>
      </ul>
    </>
  );
}
```

When the choice really needs JavaScript, render the server's version first and
switch after hydration, or use `useSyncExternalStore` with a
`getServerSnapshot` that returns the server's value.

## Prevent it with ESLint

[`no-match-media-in-render`](https://hydration.jscrate.dev/docs/rules/no-match-media-in-render),
[`no-window-render-branch`](https://hydration.jscrate.dev/docs/rules/no-window-render-branch) and
[`no-client-only-initial-state`](https://hydration.jscrate.dev/docs/rules/no-client-only-initial-state)
report the browser reads that pick a different element.

## Example

```text
  ✖ /docs 1.3s  1 error
    HP1007 Different element rendered on server and client  (screen size or media query read during render, 84%)
      #nav > select  in NavMenu
      server: "<ul>"
      client: "<select>"
      components/nav-menu.tsx:9:5
      → Use CSS media queries for layout differences, or read matchMedia / window size after mount.
```

## Related

- [HP1008: element only in the server HTML](https://hydration.jscrate.dev/docs/issues/hp1008)
- [HP1009: element missing from the server HTML](https://hydration.jscrate.dev/docs/issues/hp1009)
- [HP1010: React re-rendered a branch](https://hydration.jscrate.dev/docs/issues/hp1010)
- [Expected server HTML to contain a matching element](https://hydration.jscrate.dev/docs/errors/expected-server-html-to-contain-a-matching)
- [Client-only components](https://hydration.jscrate.dev/docs/guides/client-only-component)
