Docs

Documentation versions (currently viewingVaadin 24)

Grid

Styling API reference for the Grid component.

The look and feel of the Grid component can be customized in several ways: a set of built-in theme variants, customizable style properties and CSS selectors, and several different APIs for applying part names to header, footer and body cells.

Theme Variants

Theme variants are built-in styling variations that can be toggled on separately or in combination.

Wrap Cell Content

Cell content that overflows is normally clipped or truncated. However, the wrap-cell-content variant makes the content wrap instead.

Notice in the example here that the text in the Address cells is wrapped. If you click on the gray icon at the top right corner of the example, it opens the table in a separate browser tab. When you do that, can see the addresses on one line. You can also resize that window to see how it wraps and unwraps the text.

Open in a
new tab
@customElement('grid-wrap-cell-content')
export class Example extends LitElement {
  protected override createRenderRoot() {
    const root = super.createRenderRoot();
    // Apply custom theme (only supported if your app uses one)
    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}" theme="wrap-cell-content">
        <vaadin-grid-column
          header="Image"
          flex-grow="0"
          auto-width
          ${columnBodyRenderer(this.avatarRenderer, [])}
        ></vaadin-grid-column>
        <vaadin-grid-column path="firstName"></vaadin-grid-column>
        <vaadin-grid-column path="lastName"></vaadin-grid-column>
        <vaadin-grid-column
          header="Address"
          ${columnBodyRenderer(this.addressRenderer, [])}
        ></vaadin-grid-column>
      </vaadin-grid>
    `;
  }

  private avatarRenderer: GridColumnBodyLitRenderer<Person> = (person) => html`
    <vaadin-avatar
      img="${person.pictureUrl}"
      name="${person.firstName} ${person.lastName}"
    ></vaadin-avatar>
  `;

  private addressRenderer: GridColumnBodyLitRenderer<Person> = ({ address }) => html`
    <span>${address.street} ${address.city} ${address.zip} ${address.state}</span>
  `;
}

Tooltips can also be used to display content that doesn’t fit into the cell.

Compact

The compact theme variant makes a grid denser by reducing the header and row heights, as well as the spacing between columns.

This is useful for displaying more information on-screen without having to scroll. It can also help improve scannability and comparability between rows. Notice that there are more rows displayed in the example here, compared to the number of rows visible in the same space in the earlier example.

Open in a
new tab
<vaadin-grid .items="${this.items}" theme="compact">
  <vaadin-grid-column path="firstName"></vaadin-grid-column>
  <vaadin-grid-column path="lastName"></vaadin-grid-column>
  <vaadin-grid-column path="email"></vaadin-grid-column>
</vaadin-grid>

No Border

The no-border theme variant removes the outer border of the grid. Compare the example here with the previous one. Notice that the outer border, surrounding all of the rows is missing here.

Open in a
new tab
<vaadin-grid .items="${this.items}" theme="no-border">
  <vaadin-grid-column
    header="Image"
    flex-grow="0"
    auto-width
    ${columnBodyRenderer(this.avatarRenderer, [])}
  ></vaadin-grid-column>
  <vaadin-grid-column path="firstName"></vaadin-grid-column>
  <vaadin-grid-column path="lastName"></vaadin-grid-column>
  <vaadin-grid-column path="email"></vaadin-grid-column>
</vaadin-grid>

No Row Border

This theme variant removes the horizontal row borders. This is best suited for small datasets. Viewing larger datasets may be difficult unless paired with the row-stripes theme variant. You can see this in the example here. There’s no border between the rows. With so much space, notice how it’s a little difficult to be sure you’re reading the email address of a particular person — and not the email address of a different row. It would be worse with a wider table containing many columns of data.

Open in a
new tab
<vaadin-grid .items="${this.items}" theme="no-row-borders">
  <vaadin-grid-column
    header="Image"
    flex-grow="0"
    auto-width
    ${columnBodyRenderer(this.avatarRenderer, [])}
  ></vaadin-grid-column>
  <vaadin-grid-column path="firstName"></vaadin-grid-column>
  <vaadin-grid-column path="lastName"></vaadin-grid-column>
  <vaadin-grid-column path="email"></vaadin-grid-column>
</vaadin-grid>

Column Borders

You can add vertical borders between columns by using the column-borders theme variant. Datasets with a lot of cramped columns, or where content is truncated, can benefit from the extra separation that vertical borders bring. Compare the table here to previous ones. You can see that this one has a border between each column. While this can sometimes make reading the data easier, it can be aesthetically displeasing — look too rigid.

Open in a
new tab
<vaadin-grid .items="${this.items}" theme="column-borders">
  <vaadin-grid-column
    header="Image"
    flex-grow="0"
    auto-width
    ${columnBodyRenderer(this.avatarRenderer, [])}
  ></vaadin-grid-column>
  <vaadin-grid-column path="firstName"></vaadin-grid-column>
  <vaadin-grid-column path="lastName"></vaadin-grid-column>
  <vaadin-grid-column path="email"></vaadin-grid-column>
</vaadin-grid>

Row Stripes

The row-stripes theme variant produces a background color for every other row. This can make scanning the rows of data easier. You can see in the example here that the odd rows have a light gray background, while the even ones have none or a white background. This is particularly useful with very wide tables, with many columns of data.

Open in a
new tab
<vaadin-grid .items="${this.items}" theme="row-stripes">
  <vaadin-grid-column
    header="Image"
    flex-grow="0"
    auto-width
    ${columnBodyRenderer(this.avatarRenderer, [])}
  ></vaadin-grid-column>
  <vaadin-grid-column path="firstName"></vaadin-grid-column>
  <vaadin-grid-column path="lastName"></vaadin-grid-column>
  <vaadin-grid-column path="email"></vaadin-grid-column>
</vaadin-grid>

Style Properties

The following style properties can be used in CSS stylesheets to customize the appearance of this component.

To apply values to these properties globally in your application UI, place them in a CSS block using the html {…​} selector. See Lumo Style Properties for more information on style properties.

Feature Property Default Value

Row/cell background

--vaadin-grid-cell-background

--lumo-base-color

Row/cell padding

--vaadin-grid-cell-padding

--lumo-space-xs / --lumo-space-m

CSS Selectors

The following CSS selectors can be used in stylesheets to target the various parts and states of the component. See the Styling documentation for more details on how to style components.

Root element

vaadin-grid

Rows

Row (any)

vaadin-grid::part(row)

First row

vaadin-grid::part(first-row)

Last row

vaadin-grid::part(last-row)

Even row

vaadin-grid::part(even-row)

Odd row

vaadin-grid::part(odd-row)

Selected row

vaadin-grid::part(selected-row)

Hovered row

vaadin-grid::part(row):hover

Note: To set background colors based on row selectors, use the --vaadin-grid-cell-background style property.

Cells

Cell (any)

vaadin-grid::part(cell)

Header row cell

vaadin-grid::part(header-cell)

Body cell

vaadin-grid::part(body-cell)

Footer row cell

vaadin-grid::part(footer-cell)

Focused cell

vaadin-grid::part(focused-cell)

Cell content wrapper

vaadin-grid-cell-content

Cell in first column

vaadin-grid::part(first-column-cell)

Cell in last column

vaadin-grid::part(last-column-cell)

Cell in first row

vaadin-grid::part(first-row-cell)

Cell in last row

vaadin-grid::part(last-row-cell)

Cell in even row

vaadin-grid::part(even-row-cell)

Cell in odd row

vaadin-grid::part(odd-row-cell)

Cell in selected row

vaadin-grid::part(selected-row-cell)

Cell in first header row

vaadin-grid::part(first-header-row-cell)

Cell in last header row

vaadin-grid::part(last-header-row-cell)

Cell focus ring

vaadin-grid::part(cell)::before

Row focus ring

vaadin-grid::part(row)::before

Collapsed cell

vaadin-grid::part(collapsed-row-cell)

Expanded cell

vaadin-grid::part(expanded-row-cell)

You can style individual cells, rows, and columns by applying custom part names to them. Cell padding can be configured using the --vaadin-grid-cell-padding style property.

Selection Checkboxes

Row selection checkbox

vaadin-grid > vaadin-checkbox

Select All checkbox

vaadin-grid > #selectAllCheckbox

Sorters

Element

vaadin-grid-sorter

Active sorter

vaadin-grid-sorter[direction]

Column header content

vaadin-grid-sorter::part(content)

Sort indicators

vaadin-grid-sorter::part(indicators)

Sort indicator icons

vaadin-grid-sorter::part(indicators)::before

Sort order indicator

vaadin-grid-sorter::part(order)

Item Details

Item details cell (spans entire row)

vaadin-grid::part(details-cell)

Cell in row with open details

vaadin-grid::part(details-open-row-cell)

Row with open details

vaadin-grid::part(details-open-row)

Drag & Drop

The following terminology is used in the description of these part names:

  • Ghost: the semi-transparent copy of the row that is dragged around by the pointer.

  • Drag-source: the non-moving rows that are being dragged.

  • Drop-target: a row or a gap between rows that the ghost can be dropped onto.

The cells in the “ghost” row

vaadin-grid::part(dragstart-row-cell)

The cells in the drag source row

vaadin-grid::part(drag-source-row-cell)

The cells of a row when the drop target is between this row and the row before it (i.e., above this row)

vaadin-grid::part(dragover-above-row-cell)

The cells of a row when the drop target is between this row and the row after it (i.e., below this row)

vaadin-grid::part(dragover-below-row-cell)

The cells of a row when the drop target is the row itself (i.e., on top of this row)

vaadin-grid::part(dragover-on-top-row-cell)

The cells of a row that isn’t draggable

vaadin-grid::part(drag-disabled-row-cell)

The cells of a row that isn’t a drop target

vaadin-grid::part(drop-disabled-row-cell)

The dragged row (i.e., the “ghost”)

vaadin-grid::part(dragstart-row)

The row which is the drop target, and the target is between this row and the row before it (i.e., above this row)

vaadin-grid::part(dragover-above-row)

The row which is the drop target, and the target is between this row and the row after it (i.e., below this row)

vaadin-grid::part(dragover-below-row)

The row which is the drop target, and the target is the row itself (i.e., on top of this row)

vaadin-grid::part(dragover-on-top-row)

A row that isn’t draggable

vaadin-grid::part(drag-disabled-row)

A row that isn’t a drop target

vaadin-grid::part(drop-disabled-row)

Dynamically Styling Rows & Columns

Cells can be styled dynamically, based on application logic and the data in the grid, through custom part names. This can be used, for example, to highlight specific rows and apply custom styling to specific columns.

In the example below, bold font weight is applied to the Rating column with a font-weight-bold part name, and the rows are colored red and green, based on their rating, with low-rating and high-rating part names, respectively. The styling itself is applied in a stylesheet with vaadin-grid::part() selectors (e.g., vaadin-grid::part(high-rating)).

Open in a
new tab
interface PersonWithRating extends Person {
  customerRating: number;
}

@customElement('grid-styling')
export class Example extends LitElement {
  protected override createRenderRoot() {
    const root = super.createRenderRoot();
    // Apply custom theme (only supported if your app uses one)
    applyTheme(root);
    return root;
  }

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

  private ratingFormatter = new Intl.NumberFormat('en-US', {
    minimumFractionDigits: 2,
    maximumFractionDigits: 2,
  });

  protected override async firstUpdated() {
    const { people } = await getPeople();
    this.items = people.map((person) => ({ ...person, customerRating: Math.random() * 10 }));
  }

  protected override render() {
    return html`
      <vaadin-grid .items="${this.items}" .cellPartNameGenerator="${this.cellPartNameGenerator}">
        <vaadin-grid-column path="lastName"></vaadin-grid-column>
        <vaadin-grid-column path="profession"></vaadin-grid-column>
        <vaadin-grid-column
          header="Customer rating (0-10)"
          ${columnBodyRenderer(this.ratingRenderer, [])}
        ></vaadin-grid-column>
      </vaadin-grid>
    `;
  }

  private ratingRenderer: GridColumnBodyLitRenderer<PersonWithRating> = (person) => html`
    <span>${this.ratingFormatter.format(person.customerRating)}</span>
  `;

  private cellPartNameGenerator(column: GridColumn, model: GridItemModel<PersonWithRating>) {
    const item = model.item;
    let parts = '';
    // Make the customer rating column bold
    if (column.header?.startsWith('Customer rating')) {
      parts += ' font-weight-bold';
    }
    // Add high-rating part to customer ratings of 8 or higher
    if (item.customerRating >= 8.0) {
      parts += ' high-rating';
      // Add low-rating part to customer ratings of 4 or lower
    } else if (item.customerRating <= 4.0) {
      parts += ' low-rating';
    }
    return parts;
  }
}

Header and footer cells can be similarly styled through their own custom part names.

Open in a
new tab
interface PersonWithRating extends Person {
  customerRating: number;
}

@customElement('grid-header-footer-styling')
export class Example extends LitElement {
  protected override createRenderRoot() {
    const root = super.createRenderRoot();
    // Apply custom theme (only supported if your app uses one)
    applyTheme(root);
    return root;
  }

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

  private ratingFormatter = new Intl.NumberFormat('en-US', {
    minimumFractionDigits: 2,
    maximumFractionDigits: 2,
  });

  protected override async firstUpdated() {
    const { people } = await getPeople();
    this.items = people.map((person) => ({ ...person, customerRating: Math.random() * 10 }));
  }

  protected override render() {
    return html`
      <vaadin-grid .items="${this.items}" class="styling-header-footer">
        <vaadin-grid-column path="firstName"></vaadin-grid-column>
        <vaadin-grid-column path="lastName"></vaadin-grid-column>
        <vaadin-grid-column path="profession"></vaadin-grid-column>
        <vaadin-grid-column
          header="Customer rating (0-10)"
          header-part-name="rating-header"
          footer-part-name="rating-footer"
          ${columnFooterRenderer(() => html`<span>Avg rating: 5.32</span>`, [])}
          ${columnBodyRenderer(this.ratingRenderer, [])}
        ></vaadin-grid-column>
      </vaadin-grid>
    `;
  }

  private ratingRenderer: GridColumnBodyLitRenderer<PersonWithRating> = (person) => html`
    <span>${this.ratingFormatter.format(person.customerRating)}</span>
  `;
}