Dashboard
- Key Features
- Configuration
- Widgets
- Static Dashboards
- Dynamic, Editable Dashboards
- Dashboard Sections
- Accessibility
- Internationalization
- Related Components
A component for building static dashboard layouts and dynamic, user-configurable dashboards.
|
Note
|
Commercial Feature
A commercial Vaadin subscription is required to use Dashboard in your project. |
Source code
DashboardBasic.java
MockWidgets.java
MockWidgets.java
dashboard-basic.tsx
mock-widgets.tsx
mock-widgets.tsx
dashboard-basic.ts
mock-widgets.ts
mock-widgets.ts
Key Features
- Static & Dynamic Dashboards
-
Static: You define a dashboard and its widgets declaratively or imperatively. The React and Web Components for this are
<DashboardLayout>&<DashboardWidget>and<vaadin-dashboard-layout>&<vaadin-dashboard-widget>respectively.Dynamic: You define the data and Dashboard generates widgets using a renderer. Dynamic dashboards support edit mode that allows the end user to move, resize, and remove widgets. The React and Web Components for this are
<Dashboard>and<vaadin-dashboard>, respectively.In Flow, the
DashboardandDashboardWidgetclasses are used for both approaches. - Widgets, Columns & Rows
-
Widgets are placed in columns and rows automatically, in the order supplied, based on the dashboard’s width and the column configuration. As the dashboard’s width changes, the number of columns is automatically adjusted based on their configured minimum and maximum width, and the widget positions are adjusted so.
You can’t place a widget in a specific column or row.
- Scrolling
-
Dashboard scrolls vertically if the contents overflow its defined height. Individual widgets don’t scroll (see Widget Content Sizing).
Configuration
The following configuration options are available for the Dashboard component.
Columns & Rows
Column width can vary between a minimum and maximum size. The default maximum width is 1fr, which allows the columns to expand to fill any available space. If a fixed length value is provided, empty space is reserved at the end of rows once the columns reach their maximum width.
By default there is no limit on the number of columns, but one can be provided if needed.
The height of each dashboard row is determined by the tallest widget in that row, whose height in turn is determined by its contents. A minimum row height determines the height of empty rows, such as when a widget’s row span is stretched into an unoccupied row. The minimum height can be configured.
Source code
Java
dashboard.setMinimumColumnWidth("150px");
dashboard.setMaximumColumnWidth("300px");
dashboard.setMaximumColumnCount(4);
dashboard.setMinimumRowHeight("100px");Java
tsx
<DashboardLayout style={{
'--vaadin-dashboard-col-min-width': '150px',
'--vaadin-dashboard-col-max-width': '300px',
'--vaadin-dashboard-col-max-count': '4',
'--vaadin-dashboard-row-min-height': '100px'
}}>
...
</DashboardLayout>tsx
HTML
<vaadin-dashboard-layout style="--vaadin-dashboard-col-min-width: 150px; --vaadin-dashboard-col-max-width: 300px; --vaadin-dashboard-col-max-count: 4; --vaadin-dashboard-row-min-height: 100px">
...
</vaadin-dashboard-layout>HTML
Whitespace
The horizontal and vertical spacing between widgets, and the padding along the dashboard’s edges, can be configured.
Source code
Java
dashboard.setGap("10px");
dashboard.setPadding("20px");Java
tsx
<DashboardLayout style={{
'--vaadin-dashboard-gap': '10px',
'--vaadin-dashboard-padding': '20px',
}}>
...
</DashboardLayout>tsx
HTML
<vaadin-dashboard-layout style="--vaadin-dashboard-gap: 10px; --vaadin-dashboard-padding: 20px">
...
</vaadin-dashboard-layout>HTML
Dense Layout
This mode uses the dense packing algorithm in the CSS grid layout model. It attempts to fill in empty slots in the layout by placing smaller widgets in them. This can affect the order of the widgets. It should be used with caution in user-configurable dashboards, as the automatic reordering of widgets may be confusing during editing.
Source code
Java
dashboard.setDenseLayout(true);Java
tsx
<DashboardLayout denseLayout>
...
</DashboardLayout>tsx
HTML
<vaadin-dashboard-layout dense-layout>
...
</vaadin-dashboard-layout>HTML
Source code
dashboard-dense-layout.ts
dashboard-dense-layout.ts
Widgets
Widgets consist of a content area and a header containing the widget’s title and a slot for more elements.
Source code
DashboardWidgetContents.java
dashboard-widget-contents.tsx
dashboard-widget-contents.ts
You can set the column span and row span to make a widget take up more than one column or row in the dashboard’s layout. The actual number of columns a widget spans is limited by the current number of columns in the dashboard, however.
Widget Content Sizing
The height of a widget’s contents define its default height. The height can grow because of row span or other taller widgets on the same dashboard row. If the height of the widget is constrained (e.g., by an explicitly set height), the contents of the card can overflow. You may need to incorporate a scrollable area (e.g., with Scroller) to accommodate a height smaller than the contents you place in a widget.
The width of a widget is determined by the current column width and the widget’s column span.
Contents that should cover the entire widget area should therefore be configured with 100% width and height, as well as a minimum height corresponding to its desired default height.
Static Dashboards
Static dashboards are populated declaratively (in React and Lit) / imperatively (in Flow), like normal layouts. They are a good choice for hard-coded dashboards.
Flow |
|
React |
|
Lit Web Component |
|
Source code
DashboardBasic.java
MockWidgets.java
MockWidgets.java
dashboard-basic.tsx
mock-widgets.tsx
mock-widgets.tsx
dashboard-basic.ts
mock-widgets.ts
mock-widgets.ts
Dynamic, Editable Dashboards
Dynamic dashboards offer end users the possibility to edit the layout. Dynamic dashboards are populated through a data-binding API coupled with a widget renderer function. This makes the layout configuration easy to persist and load from storage, such as a database.
Flow |
|
React |
|
Lit Web Component |
|
Source code
DashboardEditable.java
WidgetConfig.java
DashboardStorage.java
MockWidgets.java
MockWidgets.java
dashboard-editable.tsx
WidgetConfig.java
DashboardService.java
mock-widgets.tsx
mock-widgets.tsx
dashboard-editable.ts
WidgetConfig.java
DashboardService.java
mock-widgets.ts
mock-widgets.ts
Editing
You can make dynamic dashboards editable by turning on editing mode, as seen in the sample above.
|
Note
|
Editing mode should be temporary.
The end user turns on editing mode when they want to edit the dashboard’s contents and turns it off when they finish editing. When turned off, you typically want to persist the dashboard configuration to a storage. While in editing mode, the widget contents are visible but not interactable.
|
The following operations are available in editing mode.
Widget Selection by Keyboard
In editing mode, widgets can be selected by keyboard by moving focus to the desired widget using the Tab key and pressing Space or Enter. Once selected, arrow keys can be used to move and resize widgets, and to engage the accessible move and resize modes.
Widget selection is not required for editing by pointer device.
Moving Widgets
In editing mode, widgets can be moved around by:
-
drag & drop;
-
arrow keys, once the widget has been selected;
-
an accessible move-mode engaged by clicking the drag-handle in the widget’s top left corner. Move-mode is disengaged by clicking the apply-button in the widget’s center, or by pressing Esc.
Widgets can only be moved backwards and forwards. Moving a widget past the start or end of a row moves it to the preceding or following row.
Resizing Widgets
In editing mode, widgets can be resized by increasing and decreasing their column span and row span by:
-
dragging from the drag-handle in the widget’s bottom right corner;
-
Shift + arrow keys, once the widget has been selected;
-
an accessible resize-mode engaged by clicking the resize-handle. Resize-mode is disengaged by clicking the apply-button in the widget’s center, or by pressing Esc.
Removing Widgets
In editing mode, widgets can be removed by clicking the Remove button in the widget’s top right corner.
By default, there is no confirmation step when removing a widget. You can implement a remove handler to prevent automatic removal, for example to show a confirmation dialog before actually removing the widget:
Source code
DashboardItemRemoval.java
package com.vaadin.demo.component.dashboard;
import com.vaadin.flow.component.confirmdialog.ConfirmDialog;
import com.vaadin.flow.component.dashboard.Dashboard;
import com.vaadin.flow.component.html.Div;
public class DashboardItemRemoval extends Div {
public DashboardItemRemoval() {
Dashboard dashboard = new Dashboard();
dashboard.setItemRemoveHandler(removeEvent -> {
ConfirmDialog dialog = new ConfirmDialog();
dialog.setHeader("Confirm removal");
dialog.setText("Are you sure you want to remove this item?");
dialog.setCancelable(true);
dialog.addConfirmListener(e -> removeEvent.removeItem());
dialog.open();
});
add(dashboard);
}
}
dashboard-item-removal.tsx
import { useSignal } from '@vaadin/hilla-react-signals';
import { ConfirmDialog } from '@vaadin/react-components/ConfirmDialog.js';
import type { DashboardItem, DashboardItemBeforeRemoveEvent } from '@vaadin/react-components-pro';
import { Dashboard } from '@vaadin/react-components-pro';
export function Example() {
const items = useSignal<DashboardItem[]>([
/* Item definitions */
]);
const itemToRemove = useSignal<DashboardItem | null>(null);
function handleItemBeforeRemove(event: DashboardItemBeforeRemoveEvent<DashboardItem>) {
event.preventDefault();
itemToRemove.value = event.detail.item;
}
function handleRemoveConfirm() {
items.value = items.value.filter((item) => item !== itemToRemove.value);
itemToRemove.value = null;
}
function handleRemoveCancel() {
itemToRemove.value = null;
}
return (
<>
<Dashboard
items={items.value}
onDashboardItemBeforeRemove={handleItemBeforeRemove}
></Dashboard>
<ConfirmDialog
header="Confirm removal"
cancelButtonVisible
onConfirm={handleRemoveConfirm}
onCancel={handleRemoveCancel}
opened={itemToRemove.value !== null}
>
Are you sure you want to remove this item?
</ConfirmDialog>
</>
);
}
dashboard-item-removal.ts
import '@vaadin/dashboard/vaadin-dashboard-layout.js';
import '@vaadin/dashboard/vaadin-dashboard-widget.js';
import { html, LitElement } from 'lit';
import { state } from 'lit/decorators.js';
import type { DashboardItem, DashboardItemBeforeRemoveEvent } from '@vaadin/dashboard';
export class Example extends LitElement {
@state()
items: DashboardItem[] = [
/* Item definitions */
];
@state()
itemToRemove: DashboardItem | null = null;
private handleItemBeforeRemove(event: DashboardItemBeforeRemoveEvent<DashboardItem>) {
event.preventDefault();
this.itemToRemove = event.detail.item;
}
private handleRemoveConfirm() {
this.items = this.items.filter((item) => item !== this.itemToRemove);
this.itemToRemove = null;
}
private handleRemoveCancel() {
this.itemToRemove = null;
}
render() {
return html`
<vaadin-dashboard
.items="${this.items}"
@dashboard-item-before-remove="${this.handleItemBeforeRemove}"
>
</vaadin-dashboard>
<vaadin-confirm-dialog
header="Confirm removal"
cancel-button-visible
@confirm="${this.handleRemoveConfirm}"
@cancel="${this.handleRemoveCancel}"
.opened="${this.itemToRemove !== null}"
>
Are you sure you want to remove this item?
</vaadin-confirm-dialog>
`;
}
}
Adding Widgets
Dashboard has no built-in mechanism for adding new widgets. You can implement this using an external widget selector, such as a Select drop-down, that adds the corresponding item to the dashboard.
Screen Reader Announcements
Although widget selection is announced via a widget’s title, and the various buttons all have accessible names, the component doesn’t announce changes to a widget’s position and size out of the box. These can be provided by listening to related events emitted by the component and updating custom live regions with appropriate announcements.
Source code
DashboardAnnouncements.java
MockWidgets.java
MockWidgets.java
screen-reader-only.css
screen-reader-only.css
dashboard-announcements.tsx
WidgetConfig.java
mock-widgets.tsx
mock-widgets.tsx
screen-reader-only.css
screen-reader-only.css
dashboard-announcements.ts
WidgetConfig.java
mock-widgets.ts
mock-widgets.ts
screen-reader-only.css
screen-reader-only.css
Persisting and Loading Widgets
Dynamic dashboards, with their user-editable capabilities, often require the ability to persist and load customized widget configurations to and from storage, such as a database.
The most straightforward way to persist widget configurations is by defining a custom widget/item type. This type can include custom metadata relevant to the widget content, in addition to the built-in widget/item properties.
Once you’ve defined your custom type, you can establish a mapping between your data model and the widget configuration. This involves:
-
Loading: When loading the persisted configuration, map the data from your storage to individual widget/item instances of your custom type. Each record corresponds to a single widget on the dashboard.
-
Saving: When saving the user’s customized dashboard layout, map the current configuration (e.g., column span, row span, type, custom metadata) of your dashboard’s widgets back to your data model format.
This approach allows for flexible persistence of dashboard configurations, enabling users to save and load their customized layouts across sessions.
For a simple example of how to implement this persistence approach, see the Dynamic, Editable Dashboards section above. While the example doesn’t explicitly show how to persist the data, it illustrates the concept of defining a custom type for the dashboard widgets. The specific implementation details depend on your chosen storage mechanism and data model.
Dashboard Sections
Complex dashboards can benefit from being divided into titled sections. Dashboard sections always span the full width of the dashboard, and follow the same column and row configuration as the dashboard itself. They support the same moving and removal operations in editing mode as widgets.
Source code
DashboardSections.java
dashboard-sections.tsx
dashboard-sections.ts
Accessibility
Dashboard widgets have an ARIA role of article.
Widget and section titles are rendered as headings. Root-level widgets and sections default to heading level 2 (corresponding to an <h2> element), while widgets within sections use one level below that of the section. The root heading level can be customized to match the dashboard’s correct placement in the heading hierarchy:
Source code
Flow
dashboard.setRootHeadingLevel(3);React
<Dashboard rootHeadingLevel={3}>Lit
<vaadin-dashboard root-heading-level="3">Internationalization
The following texts in the dashboard can be localized through the internationalization object:
| Property | Description |
|---|---|
| Widget selection trigger. |
| Widget deselection trigger. |
| Section selection trigger. |
| Section deselection trigger. |
| Button that engages move-mode. |
| Move forward button in move-mode. |
| Move backward button in move-mode. |
| Button that disengages move-mode. |
| Button that engages resize-mode. |
| Grow width button in resize-mode. |
| Shrink width button in resize-mode. |
| Grow height button in resize-mode. |
| Shrink height button in resize-mode. |
| Button that disengages resize-mode. |
| Remove button. |
Source code
DashboardInternationalisation.java
package com.vaadin.demo.component.dashboard;
import com.vaadin.flow.component.dashboard.Dashboard;
import com.vaadin.flow.component.html.Div;
public class DashboardInternationalisation extends Div {
public DashboardInternationalisation() {
Dashboard dashboard = new Dashboard();
Dashboard.DashboardI18n germanI18n = new Dashboard.DashboardI18n();
germanI18n.setSelectSection("Abschnitt auswählen");
germanI18n.setSelectWidget("Widget auswählen");
germanI18n.setRemove("Entfernen");
germanI18n.setResize("Größe ändern");
germanI18n.setResizeApply("Größenänderung anwenden");
germanI18n.setResizeShrinkWidth("Breite verkleinern");
germanI18n.setResizeGrowWidth("Breite vergrößern");
germanI18n.setResizeShrinkHeight("Höhe verkleinern");
germanI18n.setResizeGrowHeight("Höhe vergrößern");
germanI18n.setMove("Verschieben");
germanI18n.setMoveApply("Verschieben anwenden");
germanI18n.setMoveBackward("Nach hinten verschieben");
germanI18n.setMoveForward("Nach vorne verschieben");
dashboard.setI18n(germanI18n);
add(dashboard);
}
}
dashboard-internationalisation.tsx
import { Dashboard, type DashboardI18n } from '@vaadin/react-components-pro';
const germanI18n: DashboardI18n = {
selectSection: 'Abschnitt auswählen',
selectWidget: 'Widget auswählen',
remove: 'Entfernen',
resize: 'Größe ändern',
resizeApply: 'Größenänderung anwenden',
resizeShrinkWidth: 'Breite verkleinern',
resizeGrowWidth: 'Breite vergrößern',
resizeShrinkHeight: 'Höhe verkleinern',
resizeGrowHeight: 'Höhe vergrößern',
move: 'Verschieben',
moveApply: 'Verschieben anwenden',
moveBackward: 'Nach hinten verschieben',
moveForward: 'Nach vorne verschieben',
};
function Example() {
return <Dashboard i18n={germanI18n}></Dashboard>;
}
dashboard-internationalisation.ts
import '@vaadin/dashboard/vaadin-dashboard-layout.js';
import { html, LitElement } from 'lit';
import type { DashboardI18n } from '@vaadin/dashboard';
const germanI18n: DashboardI18n = {
selectSection: 'Abschnitt auswählen',
selectWidget: 'Widget auswählen',
remove: 'Entfernen',
resize: 'Größe ändern',
resizeApply: 'Größenänderung anwenden',
resizeShrinkWidth: 'Breite verkleinern',
resizeGrowWidth: 'Breite vergrößern',
resizeShrinkHeight: 'Höhe verkleinern',
resizeGrowHeight: 'Höhe vergrößern',
move: 'Verschieben',
moveApply: 'Verschieben anwenden',
moveBackward: 'Nach hinten verschieben',
moveForward: 'Nach vorne verschieben',
};
export class Example extends LitElement {
render() {
return html` <vaadin-dashboard-layout .i18n="${germanI18n}"></vaadin-dashboard-layout> `;
}
}