> Markdown version of [Columns](https://vaadin.com/docs/next/components/grid/columns). Section index: [llms.txt](https://vaadin.com/docs/next/components/llms.txt)

# Grid Columns

Column alignment, freezing (i.e. fixed positioning), grouping, headers and footers, visibility, and width can be configured. Users can be allowed to resize and reorder columns.

## <a id="column-alignment"></a>Column Alignment

Three different column alignments are supported: left, which is the default; center; and right.

Right alignment is useful when comparing numeric values, as it helps with readability and scannability. Notice how the *Amount* column in the example here aligns the euro values to the right. Those values would be difficult to scan visually if they were left aligned or centered. Tabular numbers — if the font offers them — or a monospace font could be used to further improve digit alignment.

**Lit** — `grid-column-alignment.ts`

```typescript
<vaadin-grid .items="${this.items}">
  <vaadin-grid-column path="displayName" header="Name"></vaadin-grid-column>
  <vaadin-grid-column
    header="Due"
    ${columnBodyRenderer(() => html`<span>${this.randomDate()}</span>`, [])}
  ></vaadin-grid-column>
  <vaadin-grid-column
    header="Amount"
    text-align="end"
    ${columnBodyRenderer(
      () => html`
        <span style="font-variant-numeric: tabular-nums">${this.randomAmount()}</span>
      `,
      []
    )}
  ></vaadin-grid-column>
</vaadin-grid>
```

**Flow** — `GridColumnAlignment.java`

```java
grid.addColumn(GridColumnAlignment::generateRandomAmountText)
        .setHeader("Amount").setTextAlign(ColumnTextAlign.END);
```

**React** — `grid-column-alignment.tsx`

```tsx
<Grid items={items.value}>
  <GridColumn path="displayName" header="Name" />
  <GridColumn path="due" header="Due" />
  <GridColumn path="amount" header="Amount" textAlign="end" />
</Grid>
```

## <a id="column-freezing"></a>Column Freezing

Columns and column groups can be frozen — made *sticky* — to exclude them from horizontally scrolling a grid. This can be useful for keeping the most important columns always visible in a grid that contains so many columns they might otherwise scroll out of view. Freezing columns at the end of the grid is useful, for example, for keeping row actions always visible.

In the example here, try scrolling the data to the right and back left. Notice that the name of each person is stationary so that you can see easily which data relates to which person as you scroll. The *Edit* button at the end also remains in place so that it’s available at any point while scrolling.

**Lit** — `grid-column-freezing.ts`

```typescript
<vaadin-grid-column
  frozen
  header="Name"
  auto-width
  flex-grow="0"
  ${columnBodyRenderer<Person>(
    (person) => html`${person.firstName} ${person.lastName}`,
    []
  )}
></vaadin-grid-column>
<vaadin-grid-column
  frozen-to-end
  auto-width
  flex-grow="0"
  ${columnBodyRenderer(
    () => html`<vaadin-button theme="tertiary-inline">Edit</vaadin-button>`,
    []
  )}
></vaadin-grid-column>
```

**Flow** — `GridColumnFreezing.java`

```java
grid.addColumn(createPersonRenderer()).setHeader("Name").setFrozen(true)
        .setAutoWidth(true).setFlexGrow(0);
grid.addColumn(createActionRenderer()).setFrozenToEnd(true)
        .setAutoWidth(true).setFlexGrow(0);
```

**React** — `grid-column-freezing.tsx`

```tsx
<GridColumn frozen header="Name" autoWidth flexGrow={0} renderer={nameRenderer} />

<GridColumn frozenToEnd autoWidth flexGrow={0} renderer={editRenderer}></GridColumn>
```

Although it’s technically possible to freeze any column, it should be used primarily to freeze columns at the start or end of the grid, leaving the remaining columns unfrozen.

## <a id="column-grouping"></a>Column Grouping

It’s possible to group columns. They share a common header and footer. Use this feature to better visualize and organize related or hierarchical data.

In the example below, the first and last names are grouped under *Name*. Whereas, the street address, the city, and so forth are grouped under *Address*.

**Lit** — `grid-column-grouping.ts`

```typescript
@customElement('grid-column-grouping')
export class Example extends LitElement {
  protected override createRenderRoot() {
    const root = super.createRenderRoot();
    applyTheme(root);
    return root;
  }

  @state()
  private items: Person[] = [];

  protected override async firstUpdated() {
    const { people } = await getPeople();
    this.items = people;
  }

  protected override render() {
    return html`
      <vaadin-grid .items="${this.items}">
        <vaadin-grid-column-group header="Name">
          <vaadin-grid-column path="firstName"></vaadin-grid-column>
          <vaadin-grid-column path="lastName"></vaadin-grid-column>
        </vaadin-grid-column-group>
        <vaadin-grid-column-group header="Address">
          <vaadin-grid-column path="address.street"></vaadin-grid-column>
          <vaadin-grid-column path="address.city"></vaadin-grid-column>
          <vaadin-grid-column path="address.zip"></vaadin-grid-column>
          <vaadin-grid-column path="address.state"></vaadin-grid-column>
        </vaadin-grid-column-group>
      </vaadin-grid>
    `;
  }
}
```

**Flow** — `GridColumnGrouping.java`

```java
Grid<Person> grid = new Grid<>(Person.class, false);
Grid.Column<Person> firstNameColumn = grid
        .addColumn(Person::getFirstName).setHeader("First name");
Grid.Column<Person> lastNameColumn = grid.addColumn(Person::getLastName)
        .setHeader("Last name");
Grid.Column<Person> streetColumn = grid
        .addColumn(person -> person.getAddress().getStreet())
        .setHeader("Street");
Grid.Column<Person> cityColumn = grid
        .addColumn(person -> person.getAddress().getCity())
        .setHeader("City");
Grid.Column<Person> zipColumn = grid
        .addColumn(person -> person.getAddress().getZip())
        .setHeader("Zip");
Grid.Column<Person> stateColumn = grid
        .addColumn(person -> person.getAddress().getState())
        .setHeader("State");

HeaderRow headerRow = grid.prependHeaderRow();
headerRow.join(firstNameColumn, lastNameColumn).setText("Name");
headerRow.join(streetColumn, cityColumn, zipColumn, stateColumn)
        .setText("Address");
```

**React** — `grid-column-grouping.tsx`

```tsx
const items = useSignal<Person[]>([]);

useEffect(() => {
  getPeople().then(({ people }) => {
    items.value = people;
  });
}, []);

return (
  <Grid items={items.value}>
    <GridColumnGroup header="Name">
      <GridColumn path="firstName" />
      <GridColumn path="lastName" />
    </GridColumnGroup>

    <GridColumnGroup header="Address">
      <GridColumn path="address.street" />
      <GridColumn path="address.city" />
      <GridColumn path="address.zip" />
      <GridColumn path="address.state" />
    </GridColumnGroup>
  </Grid>
);
```

## <a id="column-headers-footers"></a>Column Headers & Footers

Each column has a customizable header and footer. A basic column header shows the name in plain text. Footers are empty by default and therefore hidden. However, if you add content for the footer, it becomes visible. Incidentally, both the header and footer can contain rich content and components — notice the header and footer in the example here.

**Lit** — `grid-column-header-footer.ts`

```typescript
@state()
private items: Person[] = [];

protected override async firstUpdated() {
  const { people } = await getPeople();
  this.items = people.map((person) => ({
    ...person,
    displayName: `${person.firstName} ${person.lastName}`,
  }));
}

protected override render() {
  return html`
    <vaadin-grid .items="${this.items}">
      <vaadin-grid-column
        header="Name"
        path="displayName"
        ${columnFooterRenderer(() => html`<span>200 total members</span>`, [])}
      ></vaadin-grid-column>
      <vaadin-grid-column
        ${columnHeaderRenderer(this.subscriberHeaderRenderer, [])}
        ${columnBodyRenderer(this.subscriberRenderer, [])}
        ${columnFooterRenderer(() => html`<span>102 subscribers</span>`, [])}
      ></vaadin-grid-column>
      <vaadin-grid-column
        path="membership"
        ${columnHeaderRenderer(this.membershipHeaderRenderer, [])}
        ${columnFooterRenderer(() => html`<span>103 regular, 71 premium , 66 VIP</span>`, [])}
      ></vaadin-grid-column>
    </vaadin-grid>
  `;
}

private subscriberHeaderRenderer = () => html`
  <vaadin-horizontal-layout style="align-items: center; gap: var(--vaadin-gap-xs);">
    <span>Subscriber</span>
    <vaadin-icon
      icon="vaadin:info-circle"
      title="Subscribers are paying customers"
      style="--vaadin-icon-visual-size: 80%; color: var(--vaadin-text-color-secondary);"
    ></vaadin-icon>
  </vaadin-horizontal-layout>
`;

private subscriberRenderer: GridColumnBodyLitRenderer<Person> = (person) =>
  html`<span>${person.subscriber ? 'Yes' : 'No'}</span>`;

private membershipHeaderRenderer = () => html`
  <vaadin-horizontal-layout style="align-items: center; gap: var(--vaadin-gap-xs);">
    <span>Membership</span>
    <vaadin-icon
      icon="vaadin:info-circle"
      title="Membership levels determines which features a client has access to"
      style="--vaadin-icon-visual-size: 80%; color: var(--vaadin-text-color-secondary);"
    ></vaadin-icon>
  </vaadin-horizontal-layout>
`;
```

**Flow** — `GridColumnHeaderFooter.java`

```java
List<Person> people = DataService.getPeople();

Grid<Person> grid = new Grid<>(Person.class, false);
grid.addColumn(Person::getFullName).setHeader("Name")
        .setFooter(String.format("%s total members", people.size()));
grid.addColumn(person -> person.isSubscriber() ? "Yes" : "No")
        .setHeader(createSubscriberHeader())
        .setFooter(createSubscriberFooterText(people));
grid.addColumn(Person::getMembership)
        .setHeader(createMembershipHeader())
        .setFooter(createMembershipFooterText(people));

...

private static Component createSubscriberHeader() {
    Span span = new Span("Subscriber");
    Icon icon = VaadinIcon.INFO_CIRCLE.create();
    icon.getElement().setAttribute("title",
            "Subscribers are paying customers");
    icon.getStyle().set("--vaadin-icon-visual-size", "80%").set("color",
            "var(--vaadin-text-color-secondary)");

    HorizontalLayout layout = new HorizontalLayout(span, icon);
    layout.setAlignItems(FlexComponent.Alignment.CENTER);
    layout.setSpacing("var(--vaadin-gap-xs)");

    return layout;
}

private static String createSubscriberFooterText(List<Person> people) {
    long subscriberCount = people.stream().filter(Person::isSubscriber)
            .count();

    return String.format("%s subscribers", subscriberCount);
}

private static Component createMembershipHeader() {
    Span span = new Span("Membership");
    Icon icon = VaadinIcon.INFO_CIRCLE.create();
    icon.getElement().setAttribute("title",
            "Membership levels determines which features a client has access to");
    icon.getStyle().set("--vaadin-icon-visual-size", "80%").set("color",
            "var(--vaadin-text-color-secondary)");

    HorizontalLayout layout = new HorizontalLayout(span, icon);
    layout.setAlignItems(FlexComponent.Alignment.CENTER);
    layout.setSpacing("var(--vaadin-gap-xs)");

    return layout;
}

private static String createMembershipFooterText(List<Person> people) {
    long regularCount = people.stream()
            .filter(person -> "Regular".equals(person.getMembership()))
            .count();
    long premiumCount = people.stream()
            .filter(person -> "Premium".equals(person.getMembership()))
            .count();
    long vipCount = people.stream()
            .filter(person -> "VIP".equals(person.getMembership())).count();

    return String.format("%s regular, %s premium, %s VIP", regularCount,
            premiumCount, vipCount);
}
```

**React** — `grid-column-header-footer.tsx`

```tsx
function SubscriberHeader() {
  return (
    <HorizontalLayout style={{ alignItems: 'center', gap: 'var(--vaadin-gap-xs)' }}>
      <span>Subscriber</span>
      <Icon
        icon="vaadin:info-circle"
        title="Subscribers are paying customers"
        style={{ '--vaadin-icon-visual-size': '80%', color: 'var(--vaadin-text-color-secondary)' }}
      />
    </HorizontalLayout>
  );
}

function MembershipHeader() {
  return (
    <HorizontalLayout style={{ alignItems: 'center', gap: 'var(--vaadin-gap-xs)' }}>
      <span>Membership</span>
      <Icon
        icon="vaadin:info-circle"
        title="Membership levels determines which features a client has access to"
        style={{ '--vaadin-icon-visual-size': '80%', color: 'var(--vaadin-text-color-secondary)' }}
      />
    </HorizontalLayout>
  );
}

function subscriberRenderer({ item }: { item: Person }) {
  return <span>{item.subscriber ? 'Yes' : 'No'}</span>;
}

function Example() {
  const items = useSignal<Person[]>([]);

  useEffect(() => {
    getPeople().then(({ people }) => {
      items.value = people.map((person) => ({
        ...person,
        displayName: `${person.firstName} ${person.lastName}`,
      }));
    });
  }, []);

  return (
    <Grid items={items.value}>
      <GridColumn path="displayName" header="Name" footer={<span>200 total members</span>} />

      <GridColumn
        header={<SubscriberHeader />}
        footer={<span>102 subscribers</span>}
        renderer={subscriberRenderer}
      />

      <GridColumn
        path="membership"
        header={<MembershipHeader />}
        footer={<span>103 regular, 71 premium , 66 VIP</span>}
      />
    </Grid>
  );
}
```

## <a id="column-visibility"></a>Column Visibility

When you want, columns and column groups can be hidden. You can provide the user with a menu for toggling column visibilities, for example, using Popover. Allowing the user to hide columns is useful when only a subset of the columns is relevant to their task, and if there are plenty of columns.

**Lit** — `popover-anchored-dialog.ts`

```html
<vaadin-grid .items="${this.items}" style="margin-top: var(--vaadin-gap-s);">
  ${this.gridColumns.map(
    (column) => html`
      <vaadin-grid-column
        path="${column.key}"
        .hidden="${!column.visible}"
      ></vaadin-grid-column>
    `
  )}
</vaadin-grid>
```

**Flow** — `PopoverAnchoredDialog.java`

```java
Grid<Person> grid = new Grid<>(Person.class, false);
grid.addColumn(Person::getFirstName).setKey("firstName")
        .setHeader("First name");
grid.addColumn(Person::getLastName).setKey("lastName")
        .setHeader("Last name");
grid.addColumn(Person::getEmail).setKey("email").setHeader("Email");
grid.addColumn(person -> person.getAddress().getPhone()).setKey("phone")
        .setHeader("Phone");
grid.addColumn(new LocalDateRenderer<>(
        PopoverAnchoredDialog::getPersonBirthday, "yyyy-MM-dd"))
        .setKey("birthday").setHeader("Birthday");
grid.addColumn(Person::getProfession).setKey("profession")
        .setHeader("Profession");

CheckboxGroup<String> group = new CheckboxGroup<>();
group.addThemeVariants(CheckboxGroupVariant.LUMO_VERTICAL);
group.getStyle().set("padding", "0").set("margin",
        "var(--vaadin-gap-s) 0");
group.setItems(columns);
group.setItemLabelGenerator((item) -> {
    String label = StringUtils
            .join(StringUtils.splitByCharacterTypeCamelCase(item), " ");
    return StringUtils.capitalize(label.toLowerCase());
});
group.addValueChangeListener((e) -> {
    columns.stream().forEach((key) -> {
        grid.getColumnByKey(key).setVisible(e.getValue().contains(key));
    });
});
```

**React** — `popover-anchored-dialog.tsx`

```tsx
<Grid items={items.value} style={{ marginTop: 'var(--vaadin-gap-s)' }}>
  {columns.value.map((item) => (
    <GridColumn path={item.key} hidden={!item.visible} key={item.key} />
  ))}
</Grid>
```

Column visibility can also be adjusted based on the Grid’s width. For example, when the Grid is narrow, you can hide the individual columns and show a single column that combines their content.

The example below toggles the columns whenever the Grid is resized. In Flow, the width comes from the Grid’s [size signal](https://vaadin.com/docs/next/flow/ui-state/building-ui.md#element-size-signal) (since V25.3), and in Lit and React from a `ResizeObserver`. Try dragging the split handle to make the Grid narrower and see the columns change.

**Lit** — `grid-responsive-columns.ts`

```typescript
const BREAKPOINT_PX = 500;

@customElement('grid-responsive-columns')
export class Example extends LitElement {
  protected override createRenderRoot() {
    const root = super.createRenderRoot();
    applyTheme(root);
    return root;
  }

  @query('vaadin-grid')
  private grid!: Grid<Person>;

  @state()
  private items: Person[] = [];

  @state()
  private wide = true;

  // Toggles the columns whenever the grid is resized
  private resizeObserver = new ResizeObserver(([entry]) => {
    this.wide = entry.contentRect.width >= BREAKPOINT_PX;
  });

  override connectedCallback() {
    super.connectedCallback();
    this.updateComplete.then(() => this.resizeObserver.observe(this.grid));
  }

  override disconnectedCallback() {
    super.disconnectedCallback();
    this.resizeObserver.disconnect();
  }

  protected override firstUpdated() {
    getPeople().then(({ people }) => {
      this.items = people;
    });
  }

  protected override render() {
    return html`
        <vaadin-grid .items="${this.items}" style="width: 100%">
          <!-- A single column that combines the content when the grid is narrow -->
          <vaadin-grid-column
            header="Employee"
            .hidden="${this.wide}"
            ${columnBodyRenderer(this.employeeRenderer, [])}
          ></vaadin-grid-column>
          <!-- Separate columns for when the grid is wide -->
          <vaadin-grid-column
            header="Name"
            .hidden="${!this.wide}"
            ${columnBodyRenderer(this.nameRenderer, [])}
          ></vaadin-grid-column>
          <vaadin-grid-column path="profession" .hidden="${!this.wide}"></vaadin-grid-column>
          <vaadin-grid-column path="email" .hidden="${!this.wide}"></vaadin-grid-column>
        </vaadin-grid>
    `;
  }

  private employeeRenderer: GridColumnBodyLitRenderer<Person> = (person) => html`
    <b>${person.firstName} ${person.lastName}</b><br />
    <small>${person.email}</small>
  `;

  private nameRenderer: GridColumnBodyLitRenderer<Person> = (person) =>
    html`${person.firstName} ${person.lastName}`;
}
```

**Flow** — `GridResponsiveColumns.java`

```java
private static final int BREAKPOINT_PX = 500;

public GridResponsiveColumns() {
    Grid<Person> grid = new Grid<>(DataService.getPeople());

    // A single column that combines the content when the grid is narrow
    String template = "<b>${item.name}</b><br><small>${item.email}</small>";
    Grid.Column<Person> combinedColumn = grid
            .addColumn(LitRenderer.<Person> of(template)
                    .withProperty("name", Person::getFullName)
                    .withProperty("email", Person::getEmail))
            .setHeader("Employee");

    // Separate columns for when the grid is wide
    List<Grid.Column<Person>> wideColumns = List.of(
            grid.addColumn(Person::getFullName).setHeader("Name"),
            grid.addColumn(Person::getProfession).setHeader("Profession"),
            grid.addColumn(Person::getEmail).setHeader("Email"));

    // The effect runs when the grid is attached and again whenever the
    // grid is resized
    Signal<Size> size = grid.getElement().sizeSignal();
    Signal.effect(grid, () -> {
        int width = size.get().width();
        // The width is 0 until the browser has reported the size. Keep
        // all columns hidden until then.
        boolean sizeKnown = width > 0;
        boolean wide = width >= BREAKPOINT_PX;
        combinedColumn.setVisible(sizeKnown && !wide);
        wideColumns.forEach(column -> column.setVisible(sizeKnown && wide));
    });
}
```

**React** — `grid-responsive-columns.tsx`

```tsx
const BREAKPOINT_PX = 500;

const employeeRenderer = ({ item: person }: { item: Person }) => (
  <>
    <b>
      {person.firstName} {person.lastName}
    </b>
    <br />
    <small>{person.email}</small>
  </>
);

const nameRenderer = ({ item: person }: { item: Person }) => (
  <>
    {person.firstName} {person.lastName}
  </>
);

function Example() {
  const gridRef = useRef<GridElement>(null);
  const items = useSignal<Person[]>([]);
  const wide = useSignal(true);

  useEffect(() => {
    getPeople().then(({ people }) => {
      items.value = people;
    });
  }, []);

  // Toggles the columns whenever the grid is resized
  useEffect(() => {
    const resizeObserver = new ResizeObserver(([entry]) => {
      wide.value = entry.contentRect.width >= BREAKPOINT_PX;
    });
    if (gridRef.current) {
      resizeObserver.observe(gridRef.current);
    }
    return () => resizeObserver.disconnect();
  }, []);

  return (
      <Grid items={items.value} ref={gridRef} style={{ width: '100%' }}>
        {/* A single column that combines the content when the grid is narrow */}
        <GridColumn header="Employee" hidden={wide.value} renderer={employeeRenderer} />
        {/* Separate columns for when the grid is wide */}
        <GridColumn header="Name" hidden={!wide.value} renderer={nameRenderer} />
        <GridColumn path="profession" hidden={!wide.value} />
        <GridColumn path="email" hidden={!wide.value} />
      </Grid>
  );
}
```

> **Note:** In Flow, the size signal starts at 0 and only gets the real value after the browser has rendered the Grid and reported its width. This means the Grid initially uses the layout for a zero width and may briefly flash before switching to the correct one. If your application’s requirements allow it, consider basing the layout on the [window size signal](https://vaadin.com/docs/next/flow/ui-state/building-ui.md#window-size-signal) instead. Its initial value is already known when the Grid is created, so there is no flash. However, this is less flexible, because the Grid then responds to the browser window rather than to its own container.

## <a id="column-reordering-resizing"></a>Column Reordering & Resizing

Enabling the user to reorder columns is useful when they want to compare data that isn’t adjacent by default. Grouped columns can only be reordered within their group. Resizing is helpful when a column’s content doesn’t fit and is cut off or varies in length.

Instead of hiding columns as in the earlier example, in the example here the user can resize a truncated column to be able to read it, fully. Try doing that. Hover your mouse pointer over the space on the header row between the *Street* and *City* columns, until the mouse pointer changes to a resizing tool. Then, while holding the left mouse button, pull on the right edge of the *Street* column until you can see all of the street addresses.

**Lit** — `grid-column-reordering-resizing.ts`

```typescript
@customElement('grid-column-reordering-resizing')
export class Example extends LitElement {
  protected override createRenderRoot() {
    const root = super.createRenderRoot();
    applyTheme(root);
    return root;
  }

  @state()
  private items: Person[] = [];

  protected override async firstUpdated() {
    const { people } = await getPeople();
    this.items = people;
  }

  protected override render() {
    return html`
      <vaadin-grid .items="${this.items}" column-reordering-allowed>
        <vaadin-grid-column-group header="Name">
          <vaadin-grid-column path="firstName" resizable></vaadin-grid-column>
          <vaadin-grid-column path="lastName" resizable></vaadin-grid-column>
        </vaadin-grid-column-group>
        <vaadin-grid-column-group header="Address">
          <vaadin-grid-column path="address.street" resizable></vaadin-grid-column>
          <vaadin-grid-column path="address.city" resizable></vaadin-grid-column>
          <vaadin-grid-column path="address.zip" resizable></vaadin-grid-column>
          <vaadin-grid-column path="address.state" resizable></vaadin-grid-column>
        </vaadin-grid-column-group>
      </vaadin-grid>
    `;
  }
}
```

**Flow** — `GridColumnReorderingResizing.java`

```java
Grid<Person> grid = new Grid<>(Person.class, false);
grid.setColumnReorderingAllowed(true);
Grid.Column<Person> firstNameColumn = grid
        .addColumn(Person::getFirstName).setHeader("First name")
        .setResizable(true);
Grid.Column<Person> lastNameColumn = grid.addColumn(Person::getLastName)
        .setHeader("Last name").setResizable(true);
Grid.Column<Person> streetColumn = grid
        .addColumn(person -> person.getAddress().getStreet())
        .setHeader("Street").setResizable(true);
Grid.Column<Person> cityColumn = grid
        .addColumn(person -> person.getAddress().getCity())
        .setHeader("City").setResizable(true);
Grid.Column<Person> zipColumn = grid
        .addColumn(person -> person.getAddress().getZip())
        .setHeader("Zip").setResizable(true);
Grid.Column<Person> stateColumn = grid
        .addColumn(person -> person.getAddress().getState())
        .setHeader("State").setResizable(true);

HeaderRow headerRow = grid.prependHeaderRow();
headerRow.join(firstNameColumn, lastNameColumn).setText("Name");
headerRow.join(streetColumn, cityColumn, zipColumn, stateColumn)
        .setText("Address");
```

**React** — `grid-column-reordering-resizing.tsx`

```tsx
const items = useSignal<Person[]>([]);
useEffect(() => {
  getPeople().then(({ people }) => {
    items.value = people;
  });
}, []);

return (
  <Grid items={items.value} columnReorderingAllowed>
    <GridColumnGroup header="Name">
      <GridColumn path="firstName" resizable />
      <GridColumn path="lastName" resizable />
    </GridColumnGroup>

    <GridColumnGroup header="Address">
      <GridColumn path="address.street" resizable />
      <GridColumn path="address.city" resizable />
      <GridColumn path="address.zip" resizable />
      <GridColumn path="address.state" resizable />
    </GridColumnGroup>
  </Grid>
);
```

## <a id="column-width"></a>Column Width

Unless specified, all columns are the same width. You can set a specific width for any column, though, or allow the Grid to set the width based on the content. Column widths can be fixed or non-fixed, which is the default. Fixed-width columns don’t grow or shrink as the available space changes, while non-fixed-width columns do.

> **Note:** The `em` length unit is a relative setting for font sizes. Don’t use it for column widths. If the font sizes of the header, body or footer cells differ, may cause column widths to be inconsistent, leading to misaligned content. When length relative to the font size is needed, use instead the `rem` length unit — or make sure that the font size is consistent across the cells.

In the following example, the first and last columns have fixed widths. The second column’s width is set to be based on the content, while the third consumes the remaining space.

**Lit** — `grid-column-width.ts`

```typescript
@customElement('grid-column-width')
export class Example extends LitElement {
  protected override createRenderRoot() {
    const root = super.createRenderRoot();
    applyTheme(root);
    return root;
  }

  @state()
  private items: Person[] = [];

  protected override async firstUpdated() {
    const { people } = await getPeople();
    this.items = people;
  }

  protected override render() {
    return html`
      <vaadin-split-layout>
        <vaadin-grid .items="${this.items}" style="width: 100%;">
          <vaadin-grid-selection-column></vaadin-grid-selection-column>
          <vaadin-grid-column path="firstName" width="7rem" flex-grow="0"></vaadin-grid-column>
          <vaadin-grid-column path="profession" auto-width flex-grow="0"></vaadin-grid-column>
          <vaadin-grid-column path="email"></vaadin-grid-column>
          <vaadin-grid-column
            width="6rem"
            flex-grow="0"
            header="Has Sub"
            ${columnBodyRenderer<Person>((item) => html`${item.subscriber ? 'Yes' : 'No'}`, [])}
          ></vaadin-grid-column>
        </vaadin-grid>
        <div></div>
      </vaadin-split-layout>
    `;
  }
}
```

**Flow** — `GridColumnWidth.java`

```java
Grid<Person> grid = new Grid<>(Person.class, false);
grid.setSelectionMode(Grid.SelectionMode.MULTI);
grid.addColumn(Person::getFirstName).setHeader("First name")
        .setWidth("7rem").setFlexGrow(0);
grid.addColumn(Person::getProfession).setHeader("Profession")
        .setAutoWidth(true).setFlexGrow(0);
grid.addColumn(Person::getEmail).setHeader("Email");
grid.addColumn(person -> person.isSubscriber() ? "Yes" : "No")
        .setHeader("Has Sub").setWidth("6rem").setFlexGrow(0);
```

**React** — `grid-column-width.tsx`

```tsx
function Example() {
  const items = useSignal<Person[]>([]);

  useEffect(() => {
    getPeople().then(({ people }) => {
      items.value = people;
    });
  }, []);

  return (
    <SplitLayout>
      <Grid items={items.value} style={{ width: '100%' }}>
        <GridSelectionColumn />
        <GridColumn path="firstName" width="7rem" flexGrow={0} />
        <GridColumn path="profession" autoWidth flexGrow={0} />
        <GridColumn path="email" />
        <GridColumn width="6rem" flexGrow={0} header="Has Sub" renderer={subscriberRenderer} />
      </Grid>
      <div></div>
    </SplitLayout>
  );
}
```
