> Markdown version of [Master-Detail Layout](https://vaadin.com/docs/next/components/master-detail-layout). Section index: [llms.txt](https://vaadin.com/docs/next/components/llms.txt)

# Master-Detail Layout

Master-Detail Layout is component for building UIs with a horizontally or vertically split pair consisting of a master area and a detail area that can responsively switch to an overlay.

> **Important: Scaled down examples**
>
> The examples on this page are scaled down so that their viewport-size-dependent behavior can be demonstrated. Some examples also change their behavior based on your browser viewport size.

In the example below, clicking a row in the table reveals the detail area which is rendered next to the master area by default. Drag the splitter to change the width of the layout: the detail area switches to an overlay when the combined master and detail sizes exceed the available space.

**Flow** — `MasterDetailLayoutBasic.java`

```java
MasterDetailLayout layout = new MasterDetailLayout();
layout.setMasterSize("450px");
layout.setDetailSize("250px");
layout.setExpandMaster(true);
/*
 * The detail area is revealed when it's populated with a component, and
 * hidden when the component is removed:
 */
personList.getGrid().asSingleSelect().addValueChangeListener(event -> {
    Person selectedPerson = event.getValue();
    if (selectedPerson != null) {
        personDetail.setPerson(selectedPerson);
        layout.setDetail(personDetail);
    } else {
        layout.setDetail(null);
    }
});

personDetail
        .addCloseListener(event -> personList.getGrid().deselectAll());
```

**Flow** — `PersonList.java`

```java
package com.vaadin.demo.component.masterdetaillayout;

import com.vaadin.demo.domain.Person;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.html.Paragraph;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;

import java.util.List;

public class PersonList extends VerticalLayout {
    private final Grid<Person> grid;

    public PersonList(List<Person> people) {
        Paragraph paragraph = new Paragraph(
                "Select a person to view their details:");
        grid = new Grid<>();
        grid.addColumn(Person::getFirstName).setHeader("First Name");
        grid.addColumn(Person::getLastName).setHeader("Last Name");
        grid.addColumn(Person::getEmail).setHeader("Email");
        grid.addColumn(Person::getProfession).setHeader("Profession");
        grid.setItems(people);
        grid.setHeightFull();

        add(paragraph, grid);
        setHeightFull();
    }

    public Grid<Person> getGrid() {
        return grid;
    }
}
```

**Flow** — `PersonDetail.java`

```java
package com.vaadin.demo.component.masterdetaillayout;

import com.vaadin.demo.domain.Person;
import com.vaadin.flow.component.ComponentEvent;
import com.vaadin.flow.component.ComponentEventListener;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.formlayout.FormLayout;
import com.vaadin.flow.component.textfield.EmailField;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.shared.Registration;

public class PersonDetail extends VerticalLayout {
    private final FormLayout formLayout;
    private final TextField firstNameField;
    private final TextField lastNameField;
    private final EmailField emailField;
    private final TextField professionField;

    public PersonDetail() {
        formLayout = new FormLayout();

        firstNameField = new TextField("First Name");
        firstNameField.setReadOnly(true);
        lastNameField = new TextField("First Name");
        lastNameField.setReadOnly(true);
        emailField = new EmailField("Email");
        emailField.setReadOnly(true);
        professionField = new TextField("Profession");
        professionField.setReadOnly(true);

        formLayout.add(firstNameField, lastNameField, emailField,
                professionField);

        Button closeButton = new Button("Close");
        closeButton.addClickListener(
                event -> fireEvent(new CloseEvent(this, false)));

        add(formLayout, closeButton);
        setPadding(true);
    }

    public void setPerson(Person person) {
        if (person != null) {
            firstNameField.setValue(person.getFirstName());
            lastNameField.setValue(person.getLastName());
            emailField.setValue(person.getEmail());
            professionField.setValue(person.getProfession());
        } else {
            firstNameField.clear();
            lastNameField.clear();
            emailField.clear();
            professionField.clear();
        }
    }

    public static class CloseEvent extends ComponentEvent<PersonDetail> {
        public CloseEvent(PersonDetail source, boolean fromClient) {
            super(source, fromClient);
        }
    }

    public Registration addCloseListener(
            ComponentEventListener<CloseEvent> listener) {
        return addListener(CloseEvent.class, listener);
    }
}
```

**React** — `master-detail-layout-basic.tsx`

```tsx
<MasterDetailLayout masterSize="450px" detailSize="250px" expandMaster>
  <MasterDetailLayout.Master>
    <PersonList
      people={items.value}
      selectedPerson={selectedPerson.value}
      onSelect={(person) => {
        selectedPerson.value = person;
      }}
    />
  </MasterDetailLayout.Master>
  <MasterDetailLayout.Detail>
    {/* The detail area is revealed when it's populated with a component,
        and hidden when the component is removed: */}
    {selectedPerson.value ? (
      <PersonDetail
        person={selectedPerson.value}
        onClose={() => {
          selectedPerson.value = null;
        }}
      />
    ) : null}
  </MasterDetailLayout.Detail>
</MasterDetailLayout>
```

**React** — `PersonList.tsx`

```tsx
import React from 'react';
import { Grid, GridColumn, VerticalLayout } from '@vaadin/react-components';
import type Person from 'Frontend/generated/com/vaadin/demo/domain/Person';

interface PersonListProps {
  people: Person[];
  selectedPerson: Person | null;

  onSelect(person: Person | null): void;
}

function PersonList({ people, selectedPerson, onSelect }: PersonListProps) {
  return (
    <VerticalLayout style={{ height: '100%', border: '1px solid var(--vaadin-border-color)' }}>
      <div style={{ padding: 'var(--vaadin-padding-m)', fontWeight: 'bold' }}>
        Select a person to view their details:
      </div>
      <Grid
        theme="no-border"
        items={people}
        style={{ height: '100%' }}
        selectedItems={selectedPerson ? [selectedPerson] : []}
        onActiveItemChanged={(e) => {
          onSelect(e.detail.value ?? null);
        }}
      >
        <GridColumn path="firstName" />
        <GridColumn path="lastName" />
        <GridColumn path="email" />
        <GridColumn path="profession" />
      </Grid>
    </VerticalLayout>
  );
}

export default PersonList;
```

**React** — `PersonDetail.tsx`

```tsx
import React from 'react';
import {
  Button,
  EmailField,
  FormLayout,
  TextField,
  VerticalLayout,
} from '@vaadin/react-components';
import type Person from 'Frontend/generated/com/vaadin/demo/domain/Person';

interface PersonDetailProps {
  person: Person | null;
  onClose(): void;
}

function PersonDetail({ person, onClose }: PersonDetailProps) {
  return (
    <VerticalLayout theme="padding">
      <FormLayout>
        <TextField label="First Name" value={person?.firstName ?? ''} readonly />
        <TextField label="Last Name" value={person?.lastName ?? ''} readonly />
        <EmailField label="Email" value={person?.email ?? ''} readonly />
        <TextField label="Profession" value={person?.profession ?? ''} readonly />
      </FormLayout>
      <Button onClick={onClose} style={{ marginTop: 'var(--vaadin-gap-l)' }}>
        Close
      </Button>
    </VerticalLayout>
  );
}

export default PersonDetail;
```

## <a id="setting-sizes"></a>Setting Sizes

The `masterSize` and `detailSize` properties define sizes for each area. When the combined master and detail sizes exceed the available space, the detail area switches to an overlay.

**Flow**

```Java
layout.setMasterSize("600px");
layout.setDetailSize("300px");
```

**React**

```tsx
<MasterDetailLayout
  masterSize="600px"
  detailSize="300px"
/>
```

The default `masterSize` is `30rem`. If `detailSize` is not set, it is automatically determined based on the detail content’s minimum width and cached until new detail content is set.

> **Note:** In earlier versions, `setMasterMinSize` and `setDetailMinSize` were used to set minimum sizes for each area. These have been replaced by `setMasterSize` / `setDetailSize` overloads that accept a boolean `expand` parameter, controlling whether the area expands to fill remaining space.

### <a id="expanding-the-master-or-detail-area"></a>Expanding the Master or Detail Area (since V25.2)

By default, both areas have fixed sizes. The master or detail area can be configured to expand and fill any available space beyond the specified sizes. When both are set to expand, they share the available space equally.

**Flow**

```Java
// Detail area expands to fill remaining space
layout.setMasterSize("300px");
layout.setDetailSize("400px", true);

// Both areas expand proportionally
layout.setExpandMaster(true);
layout.setExpandDetail(true);
```

**React**

```tsx
{/* Detail area expands to fill remaining space */}
<MasterDetailLayout
  masterSize="300px"
  detailSize="400px"
  expandDetail
/>

{/* Both areas expand proportionally */}
<MasterDetailLayout
  masterSize="300px"
  detailSize="400px"
  expandMaster
  expandDetail
/>
```

These settings apply only while the master and detail areas are shown side by side. In overlay mode, the detail’s size is controlled separately by `overlaySize`, as described in the next section.

## <a id="overlay-size"></a>Overlay Size (since V25.2)

By default, the detail overlay uses the detail size. The overlay size can be customized separately using `overlaySize`. For example, setting it to `100%` makes the detail overlay cover the full layout.

> **Note:** In earlier versions, the same effect was achieved using `setOverlayMode(OverlayMode)` (Flow) / `stackOverlay` (React), which has been replaced by `overlaySize`.

**Flow**

```Java
layout.setOverlaySize("100%");
```

**React**

```tsx
<MasterDetailLayout overlaySize="100%" />
```

## <a id="forced-overlay-mode"></a>Forced Overlay Mode

The layout can be configured to *always* render the detail area as an overlay, even if there is enough space for master and detail to be shown next to each other using the default (split) mode:

**Flow**

```Java
layout.setForceOverlay(true);
```

**React**

```tsx
<MasterDetailLayout forceOverlay />
```

## <a id="overlay-containment-modes"></a>Overlay Containment Modes

The overlay can be configured to render in two different ways, called *containment modes*:

- **Layout**: The overlay only covers the master area (default);

- **Page**: The overlay covers the entire page.

**Flow**

```Java
layout.setOverlayContainment(MasterDetailLayout.OverlayContainment.PAGE);
```

**React**

```tsx
<MasterDetailLayout overlayContainment="page" />
```

> **Note: Limited Modality with Page-Containment**
>
> The modality of the detail overlay in page containment mode is currently limited to pointer interactions. Elements behind the overlay can be focused by keyboard.

## <a id="orientation"></a>Orientation

By default, the Master-Detail Layout is split horizontally. This can be changed to a vertical split.

**Flow** — `MasterDetailLayoutVertical.java`

```java
MasterDetailLayout layout = new MasterDetailLayout();
layout.setOrientation(MasterDetailLayout.Orientation.VERTICAL);
```

**Flow** — `PersonList.java`

```java
package com.vaadin.demo.component.masterdetaillayout;

import com.vaadin.demo.domain.Person;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.html.Paragraph;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;

import java.util.List;

public class PersonList extends VerticalLayout {
    private final Grid<Person> grid;

    public PersonList(List<Person> people) {
        Paragraph paragraph = new Paragraph(
                "Select a person to view their details:");
        grid = new Grid<>();
        grid.addColumn(Person::getFirstName).setHeader("First Name");
        grid.addColumn(Person::getLastName).setHeader("Last Name");
        grid.addColumn(Person::getEmail).setHeader("Email");
        grid.addColumn(Person::getProfession).setHeader("Profession");
        grid.setItems(people);
        grid.setHeightFull();

        add(paragraph, grid);
        setHeightFull();
    }

    public Grid<Person> getGrid() {
        return grid;
    }
}
```

**Flow** — `PersonDetail.java`

```java
package com.vaadin.demo.component.masterdetaillayout;

import com.vaadin.demo.domain.Person;
import com.vaadin.flow.component.ComponentEvent;
import com.vaadin.flow.component.ComponentEventListener;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.formlayout.FormLayout;
import com.vaadin.flow.component.textfield.EmailField;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.shared.Registration;

public class PersonDetail extends VerticalLayout {
    private final FormLayout formLayout;
    private final TextField firstNameField;
    private final TextField lastNameField;
    private final EmailField emailField;
    private final TextField professionField;

    public PersonDetail() {
        formLayout = new FormLayout();

        firstNameField = new TextField("First Name");
        firstNameField.setReadOnly(true);
        lastNameField = new TextField("First Name");
        lastNameField.setReadOnly(true);
        emailField = new EmailField("Email");
        emailField.setReadOnly(true);
        professionField = new TextField("Profession");
        professionField.setReadOnly(true);

        formLayout.add(firstNameField, lastNameField, emailField,
                professionField);

        Button closeButton = new Button("Close");
        closeButton.addClickListener(
                event -> fireEvent(new CloseEvent(this, false)));

        add(formLayout, closeButton);
        setPadding(true);
    }

    public void setPerson(Person person) {
        if (person != null) {
            firstNameField.setValue(person.getFirstName());
            lastNameField.setValue(person.getLastName());
            emailField.setValue(person.getEmail());
            professionField.setValue(person.getProfession());
        } else {
            firstNameField.clear();
            lastNameField.clear();
            emailField.clear();
            professionField.clear();
        }
    }

    public static class CloseEvent extends ComponentEvent<PersonDetail> {
        public CloseEvent(PersonDetail source, boolean fromClient) {
            super(source, fromClient);
        }
    }

    public Registration addCloseListener(
            ComponentEventListener<CloseEvent> listener) {
        return addListener(CloseEvent.class, listener);
    }
}
```

**React** — `master-detail-layout-vertical.tsx`

```tsx
<MasterDetailLayout orientation="vertical" masterSize="150px" detailSize="250px" expandMaster>
</MasterDetailLayout>
```

**React** — `PersonList.tsx`

```tsx
import React from 'react';
import { Grid, GridColumn, VerticalLayout } from '@vaadin/react-components';
import type Person from 'Frontend/generated/com/vaadin/demo/domain/Person';

interface PersonListProps {
  people: Person[];
  selectedPerson: Person | null;

  onSelect(person: Person | null): void;
}

function PersonList({ people, selectedPerson, onSelect }: PersonListProps) {
  return (
    <VerticalLayout style={{ height: '100%', border: '1px solid var(--vaadin-border-color)' }}>
      <div style={{ padding: 'var(--vaadin-padding-m)', fontWeight: 'bold' }}>
        Select a person to view their details:
      </div>
      <Grid
        theme="no-border"
        items={people}
        style={{ height: '100%' }}
        selectedItems={selectedPerson ? [selectedPerson] : []}
        onActiveItemChanged={(e) => {
          onSelect(e.detail.value ?? null);
        }}
      >
        <GridColumn path="firstName" />
        <GridColumn path="lastName" />
        <GridColumn path="email" />
        <GridColumn path="profession" />
      </Grid>
    </VerticalLayout>
  );
}

export default PersonList;
```

**React** — `PersonDetail.tsx`

```tsx
import React from 'react';
import {
  Button,
  EmailField,
  FormLayout,
  TextField,
  VerticalLayout,
} from '@vaadin/react-components';
import type Person from 'Frontend/generated/com/vaadin/demo/domain/Person';

interface PersonDetailProps {
  person: Person | null;
  onClose(): void;
}

function PersonDetail({ person, onClose }: PersonDetailProps) {
  return (
    <VerticalLayout theme="padding">
      <FormLayout>
        <TextField label="First Name" value={person?.firstName ?? ''} readonly />
        <TextField label="Last Name" value={person?.lastName ?? ''} readonly />
        <EmailField label="Email" value={person?.email ?? ''} readonly />
        <TextField label="Profession" value={person?.profession ?? ''} readonly />
      </FormLayout>
      <Button onClick={onClose} style={{ marginTop: 'var(--vaadin-gap-l)' }}>
        Close
      </Button>
    </VerticalLayout>
  );
}

export default PersonDetail;
```

## <a id="animation"></a>Animation

When detail content is added, replaced, or removed, the layout animates the transition. The detail area slides and fades into place next to the master area, or slides in from the edge with a backdrop when shown as an overlay. Animations are skipped automatically when the user’s operating system requests reduced motion.

Animations are enabled by default. They can be disabled, for example to make detail changes apply instantly:

**Flow**

```Java
layout.setAnimationEnabled(false);
```

**React**

```tsx
<MasterDetailLayout noAnimation />
```

> **Note: Avoiding Animation on Initial Render in React**
>
> When detail content is rendered conditionally based on state that is loaded asynchronously, for example in a `useEffect` hook, the detail appears after the layout has already rendered. The layout detects this as a change and animates the detail in, even though it is really the initial content. To avoid this, render the entire layout only once the data is available, instead of rendering the layout first and adding the detail afterwards.

## <a id="detail-placeholder"></a>Detail Placeholder (since V25.2)

A placeholder component can be shown in the detail area when no detail content is set. Unlike detail content, the placeholder simply hides when it doesn’t fit rather than being rendered as an overlay.

**Flow**

```Java
layout.setDetailPlaceholder(new Span("Select an item to see details"));
```

**React**

```tsx
<MasterDetailLayout>
  <MasterDetailLayout.Master>
    {/* master content */}
  </MasterDetailLayout.Master>
  <MasterDetailLayout.Detail>
    {selectedItem && <ItemDetail item={selectedItem} />}
  </MasterDetailLayout.Detail>
  <MasterDetailLayout.DetailPlaceholder>
    Select an item to see details
  </MasterDetailLayout.DetailPlaceholder>
</MasterDetailLayout>
```

## <a id="hiding-the-detail-area"></a>Hiding the Detail Area

The detail area is automatically shown or hidden when content is added or removed from it. There is no explicit property to control the visibility of the detail area.

Master-Detail Layout exposes the following events that can be used to hide the detail area by removing the content from it:

- `backdrop-click`: Fired when the user clicks on the backdrop of the detail overlay when it is in drawer mode.

- `detail-escape-press`: Fired when the user presses `Escape` within the detail area.

**Flow**

```java
layout.addBackdropClickListener(event -> {
    // Hide the detail area by removing the content
    layout.setDetail(null);
});

layout.addDetailEscapePressListener(event -> {
    // Hide the detail area by removing the content
    layout.setDetail(null);
});
```

**React**

```tsx
const selectedPerson = useSignal<Person | null>(null);

const closeDetail = () => {
  // Clear selected person to not render the detail content
  selectedPerson.value = null;
};

<MasterDetailLayout onBackdropClick={closeDetail} onDetailEscapePress={closeDetail}>
  <MasterDetailLayout.Master>
    <PersonList onSelect={(person) => { selectedPerson.value = person}} />
  </MasterDetailLayout.Master>
  <MasterDetailLayout.Detail>
    {selectedPerson.value && <PersonDetail person={selectedPerson.value} />}
  </MasterDetailLayout.Detail>
</MasterDetailLayout>
```

## <a id="router-integration"></a>Router Integration

Master-Detail Layout can be used as a router layout (see [Flow](https://vaadin.com/docs/next/flow/routing/layout.md)/[Hilla](https://vaadin.com/docs/next/hilla/guides/routing.md#adding-layout-routes)), so that nested views are automatically rendered in the details area of the component. This allows showing nested views without having to manage the contents of the details area manually when the route changes, while providing the same benefits such as responsive behavior that the component normally provides.

The Flow `MasterDetailLayout` component implements the `RouterLayout` interface. When using a view class that extends from `MasterDetailLayout` as a layout for a nested view, that view is then automatically shown in the details area of the component.

The example below shows how to set up a master and a detail view. The master view is `ProductListView`, which would show a list of products, and the detail view is `ProductDetailView`, which shows information about a specific product. The `ProductListView` extends from `MasterDetailLayout`, so that it can be used as a route layout by the detail view. It also configures a `@Route` so that it can be navigated to by itself. Assuming there is a main layout for the application, for example one using `AppLayout`, it configures that as a parent layout. The `ProductDetailView` configures a `@Route`, using the `ProductListView` as the route layout.

With this setup, when navigating to `/products`, the layout would only show the product list. When navigating to a product detail, for example `/products/1`, it would then also show the product details next to, or on top of, the product list.

**Flow**

```java
@ParentLayout(MainLayout.class)
@Route(value = "products", layout = MainLayout.class)
public class ProductListView extends MasterDetailLayout { ... }

@Route(value = "products/:productId", layout = ProductListView.class)
public class ProductDetailView extends VerticalLayout { ... }
```

The `MasterDetailLayout` React component can be integrated with Hilla’s routing mechanism, so that any nested views automatically show up in the details area of the component.

The example below shows how to set up a master and a detail view. The master view is `ProductListView`, which would show a list of products, and the detail view is `ProductDetailView`, which shows information about a specific product. `ProductListView` is a layout component that renders a `MasterDetailLayout` component. The layout component retrieves the nested child view using `useOutlet` and renders it into the details area. Depending on whether a nested child view is present, `MasterDetailLayout` will automatically show or hide the details area. `ProductDetailView` is just a regular component, there is no specific configuration necessary to make it work. `EmptyProductDetailView` returns `null` so that the detail area is closed by default.

With this setup, when navigating to `/products`, the layout would only show the product list. When navigating to a product detail, for example `/products/1`, it would then also show the product details next to, or on top of, the product list.

**Hilla**

```tsx
// frontend/views/products/@layout.tsx
import { useOutlet } from 'react-router';

export default function ProductListView() {
  const childView = useOutlet();

  return (
    <MasterDetailLayout>
      <MasterDetailLayout.Master>
        <ProductList />
      </MasterDetailLayout.Master>
      <MasterDetailLayout.Detail>
        { childView }
      </MasterDetailLayout.Detail>
    </MasterDetailLayout>
  );
}

// frontend/views/products/@index.tsx
export default function EmptyProductDetailView() {
  return null
}

// frontend/views/products/{productId}/@index.tsx
export default function ProductDetailView() { ... }
```

## <a id="nesting-layouts"></a>Nesting Layouts

Master-Detail Layouts can be nested inside each other to enable multi-level navigation.

When nesting, it may help to omit the `detailSize` property to let layouts automatically calculate detail sizes based on their content’s minimum size. In this case, each nested detail pushes its minimum size up to its ancestors, so when space gets tight, the outermost levels collapse into overlays first, keeping the levels closest to the user fully visible as long as possible.

In contrast, with explicitly set sizes, the order is reversed: collapsing starts from the innermost levels, keeping the outermost ones open instead, which might cause the user to lose some context.

**Flow** — `MasterDetailLayoutNested.java`

```java
// Outer layout: professions → people
MasterDetailLayout outerLayout = new MasterDetailLayout();
outerLayout.setMasterSize("300px");
outerLayout.setExpandDetail(true);

// Inner layout: people → person details
MasterDetailLayout innerLayout = new MasterDetailLayout();
innerLayout.setMasterSize("500px");
innerLayout.setExpandDetail(true);

// Profession grid (outer master)
Grid<String> professionGrid = new Grid<>();
professionGrid.addColumn(p -> p).setHeader("Profession");
professionGrid.setItems(professions);
professionGrid.asSingleSelect().bindValue(selectedProfession,
        selectedProfession::set);
professionGrid.setHeightFull();
professionGrid.addThemeVariants(GridVariant.NO_BORDER);
outerLayout.setMaster(professionGrid);

// Person grid (inner master)
Grid<Person> personGrid = new Grid<>();
personGrid.addColumn(Person::getFirstName).setHeader("First Name");
personGrid.addColumn(Person::getLastName).setHeader("Last Name");
personGrid.addColumn(Person::getEmail).setHeader("Email");
personGrid.asSingleSelect().bindValue(selectedPerson,
        selectedPerson::set);
personGrid.setHeightFull();
personGrid.addThemeVariants(GridVariant.NO_BORDER);
innerLayout.setMaster(personGrid);

// Person detail form (inner detail)
TextField firstNameField = new TextField("First Name");
firstNameField.setReadOnly(true);
firstNameField.bindValue(
        selectedPerson.map((p) -> p != null ? p.getFirstName() : ""),
        null);

TextField lastNameField = new TextField("Last Name");
lastNameField.setReadOnly(true);
lastNameField.bindValue(
        selectedPerson.map((p) -> p != null ? p.getLastName() : ""),
        null);

EmailField emailField = new EmailField("Email");
emailField.setReadOnly(true);
emailField.bindValue(
        selectedPerson.map((p) -> p != null ? p.getEmail() : ""), null);

Button closeButton = new Button("Close",
        event -> selectedPerson.set(null));

FormLayout formLayout = new FormLayout();
formLayout.setAutoResponsive(true);
formLayout.getStyle().set("padding-inline", "var(--vaadin-gap-m)");
formLayout.add(firstNameField, lastNameField, emailField, closeButton);

Signal.effect(personGrid, () -> {
    personGrid.setItems(people.get());
    selectedPerson.set(null);
});

Signal.effect(outerLayout, () -> {
    outerLayout.setDetail(
            selectedProfession.get() != null ? innerLayout : null);
});

Signal.effect(innerLayout, () -> {
    innerLayout.setDetail(
            selectedPerson.get() != null ? formLayout : null);
});

// Close detail on backdrop click or Escape
outerLayout.addBackdropClickListener(
        event -> selectedProfession.set(null));
outerLayout.addDetailEscapePressListener(
        event -> selectedProfession.set(null));
innerLayout.addBackdropClickListener(event -> selectedPerson.set(null));
innerLayout.addDetailEscapePressListener(
        event -> selectedPerson.set(null));
add(outerLayout);
```

**React** — `master-detail-layout-nested.tsx`

```tsx
<MasterDetailLayout
  masterSize="300px"
  expandDetail
  onBackdropClick={() => {
    selectedProfession.value = null;
  }}
  onDetailEscapePress={() => {
    selectedProfession.value = null;
  }}
>
  <MasterDetailLayout.Master>
    <Grid
      items={professions.value}
      selectedItems={selectedProfession.value ? [selectedProfession.value] : []}
      onActiveItemChanged={(e) => {
        selectedProfession.value = e.detail.value ?? null;
        selectedPerson.value = null;
      }}
      theme="no-border"
      style={{ height: '100%' }}
    >
      <GridColumn header="Profession">{({ item }) => <>{item}</>}</GridColumn>
    </Grid>
  </MasterDetailLayout.Master>
  <MasterDetailLayout.Detail>
    {selectedProfession.value ? (
      <MasterDetailLayout
        masterSize="500px"
        expandDetail
        onBackdropClick={() => {
          selectedPerson.value = null;
        }}
        onDetailEscapePress={() => {
          selectedPerson.value = null;
        }}
      >
        <MasterDetailLayout.Master>
          <Grid
            items={persons.value}
            selectedItems={selectedPerson.value ? [selectedPerson.value] : []}
            onActiveItemChanged={(e) => {
              selectedPerson.value = e.detail.value ?? null;
            }}
            theme="no-border"
            style={{ height: '100%' }}
          >
            <GridColumn path="firstName" />
            <GridColumn path="lastName" />
            <GridColumn path="email" />
          </Grid>
        </MasterDetailLayout.Master>
        <MasterDetailLayout.Detail>
          {selectedPerson.value ? (
            <FormLayout autoResponsive style={{ paddingInline: 'var(--vaadin-gap-m)' }}>
              <TextField label="First Name" value={selectedPerson.value.firstName} readonly />
              <TextField label="Last Name" value={selectedPerson.value.lastName} readonly />
              <EmailField label="Email" value={selectedPerson.value.email} readonly />
              <Button
                onClick={() => {
                  selectedPerson.value = null;
                }}
              >
                Close
              </Button>
            </FormLayout>
          ) : null}
        </MasterDetailLayout.Detail>
      </MasterDetailLayout>
    ) : null}
  </MasterDetailLayout.Detail>
</MasterDetailLayout>
```

## <a id="related-components"></a>Related Components

| Component                                                               | Usage Recommendation                                                           |
| ----------------------------------------------------------------------- | ------------------------------------------------------------------------------ |
| [Split Layout](https://vaadin.com/docs/next/components/split-layout.md) | A component with two content areas and a draggable resize handle between them. |
