Layout Model
The dashboard layout is a hierarchical model: DashboardLayout contains DashboardRow instances, each row contains DashboardColumn instances, and each column can hold one ComponentInstance.
Hierarchy
DashboardLayout
The top-level container for the entire dashboard. It holds an ordered list of rows plus optional metadata.
class DashboardLayout {
final String version; // Schema version (default: '2.0.0')
final List<DashboardRow> rows; // Ordered list of rows
final Map<String, dynamic>? metadata; // Optional metadata (name, apiBaseUrl, etc.)
// Constants
static const int maxRows = 25;
// Queries
bool get canAddRow; // rows.length < maxRows
int get rowCount; // Number of rows
bool get isEmpty; // No rows
int get componentCount; // Total filled columns across all rows
int get totalColumns; // Total columns across all rows
int get emptySlotCount; // Total empty columns
String? get apiBaseUrl; // API base URL from metadata
// Factories
factory DashboardLayout.empty();
factory DashboardLayout.withRows(List<DashboardRow> rows);
// Mutation
DashboardLayout copyWith({...});
DashboardLayout withApiBaseUrl(String baseUrl);
// Serialization
factory DashboardLayout.fromJson(
Map<String, dynamic> json, {
required DashboardEditorRegistry registry,
});
Map<String, dynamic> toJson();
}JSON Structure
{
"version": "2.0.0",
"rows": [
{
"id": "row-stats",
"order": 0,
"title": "Enrollment Overview",
"height": 140,
"columns": [
{
"id": "col-1",
"flex": 2,
"component": {
"id": "comp-1",
"componentKey": "lms_stat_card",
"title": "Total Students",
"properties": {
"title": "Total Students",
"value": 1247
}
}
}
]
}
],
"metadata": {
"apiBaseUrl": "https://api.lms.example.com"
}
}DashboardRow
A horizontal container for 1-6 columns. Rows stack vertically and are ordered by their order field.
class DashboardRow {
final String id; // Unique row ID
final List<DashboardColumn> columns; // 1-6 columns
final int order; // Display order (0-based)
final String? title; // Optional display title
final double? height; // Fixed height in pixels (null = default)
final double? viewportFraction; // Viewport-relative height (0.1+)
final Map<String, dynamic>? metadata; // Optional metadata
// Constants
static const int maxColumns = 6;
// Height
double get effectiveHeight; // height ?? 60.0
bool get usesViewportFraction; // viewportFraction != null
// Column queries
bool get canAddColumn; // columns.length < 6
bool get hasEmptyColumns;
bool get isCompletelyEmpty;
bool get isCompletelyFilled;
int get columnCount;
int get totalFlex; // Sum of all column flex values
List<DashboardColumn> get emptyColumns;
List<DashboardColumn> get filledColumns;
// Factories
factory DashboardRow.single(String id, int order, String columnId);
factory DashboardRow.withColumns(String id, int order, List<String> columnIds);
// Flex distribution
static List<int> distributeFlex(int count); // Evenly distribute 6 flex units
DashboardRow normalizedFlex(); // Normalize to sum to 6
}Row Sizing
Rows support two sizing modes:
Fixed height (default): The row has a height in pixels, set via the height field. If null, the default minimum of 60 pixels is used.
Viewport fraction: When viewportFraction is set (0.1 or greater), the row height becomes a fraction of the viewport height. This is useful for full-screen dashboard rows.
// Fixed height row
DashboardRow(id: 'r1', order: 0, height: 200, columns: [...]);
// Viewport-relative row (takes 50% of screen)
DashboardRow(id: 'r2', order: 1, viewportFraction: 0.5, columns: [...]);Flex Distribution
When creating a row with multiple columns, flex values are distributed evenly. With 6 total flex units:
| Columns | Flex Distribution | Widths |
|---|---|---|
| 1 | [6] | 100% |
| 2 | [3, 3] | 50%, 50% |
| 3 | [2, 2, 2] | 33%, 33%, 33% |
| 4 | [2, 2, 1, 1] | 33%, 33%, 17%, 17% |
| 5 | [2, 1, 1, 1, 1] | 33%, 17%, 17%, 17%, 17% |
| 6 | [1, 1, 1, 1, 1, 1] | 17% each |
When the column count does not divide evenly into 6, the remainder is distributed to leading columns.
DashboardColumn
A vertical slot within a row that holds zero or one component. Columns use a flex factor for relative sizing.
class DashboardColumn {
final String id; // Unique column ID
final int flex; // Flex factor (1-6), default: 1
final ComponentInstance? component; // Component (null if empty)
// Queries
bool get isEmpty; // component == null
bool get hasComponent; // component != null
// Factories
factory DashboardColumn.empty(String id, {int flex = 1});
factory DashboardColumn.withComponent(String id, ComponentInstance component, {int flex = 1});
// Mutation
DashboardColumn copyWith({String? id, int? flex, ComponentInstance? component, bool clearComponent});
}Flex Sizing
Columns within a row must sum to DashboardLayoutConstants.totalFlexPerRow (6). The engine automatically normalizes flex values when columns are added or removed.
// Three equal columns: 2 + 2 + 2 = 6
DashboardColumn(id: 'a', flex: 2, component: componentA),
DashboardColumn(id: 'b', flex: 2, component: componentB),
DashboardColumn(id: 'c', flex: 2, component: componentC),
// Two columns, 2:1 ratio: 4 + 2 = 6
DashboardColumn(id: 'a', flex: 4, component: chartComponent),
DashboardColumn(id: 'b', flex: 2, component: sidebarComponent),ComponentInstance
A component instance placed inside a column. It references a registered component by key and stores its configuration as a PropertyCollection.
class ComponentInstance {
final String id; // Unique component ID
final String componentKey; // References registered component
final String? title; // Optional display title
final PropertyCollection properties; // Type-safe component config
final Map<String, dynamic>? metadata; // Optional metadata
// Factory with defaults from the scoped dashboard registry
factory ComponentInstance.withDefaults({
required String id,
required String componentKey,
required DashboardEditorRegistry registry,
String? title,
Map<String, dynamic>? configOverrides,
});
}Creating Components
The withDefaults factory looks up the component in the DashboardEditorRegistry, calls its createProperties() factory, and applies any overrides:
// Component with default properties from the component descriptor
final component = ComponentInstance.withDefaults(
id: 'comp-enrollment',
componentKey: 'lms_stat_card',
registry: registry,
title: 'Total Enrolled Students',
configOverrides: {
'title': 'Total Enrolled',
'value': 1247,
'trend': '+12%',
},
);Component Serialization
Components serialize their PropertyCollection under the properties key. During deserialization, the component looks up its descriptor in the registry to recreate the proper PropertyCollection type:
{
"id": "comp-enrollment",
"componentKey": "lms_stat_card",
"title": "Total Enrolled Students",
"properties": {
"title": "Total Enrolled",
"value": 1247,
"trend": "+12%"
}
}If the component key is not found in the registry during deserialization (for example, the component was unregistered), the component is created with an empty PropertyCollection and the renderer shows an error widget.
Layout Constants
The DashboardLayoutConstants class centralizes all sizing and spacing values:
| Constant | Value | Purpose |
|---|---|---|
cellGap | 16.0 | Gap between rows and columns |
minCellHeight | 60.0 | Default minimum row height |
maxCellHeight | 800.0 | Maximum row height |
totalFlexPerRow | 6 | Flex units per row |
minColumnFlex | 1 | Minimum flex per column |
editorInset | 12.0 | Inset padding in editor mode |
toolbarHeight | 48.0 | Editor toolbar height |
resizeHandleHeight | 8.0 | Height of row resize handles |
LMS Example: Enrollment Dashboard Layout
Here is a complete LMS enrollment dashboard layout with three rows:
final enrollmentDashboard = DashboardLayout(
metadata: {'apiBaseUrl': 'https://api.lms.example.com'},
rows: [
// Row 1: KPI stat cards (3 equal columns)
DashboardRow(
id: 'row-kpis',
order: 0,
title: 'Key Metrics',
height: 140,
columns: [
DashboardColumn.withComponent('col-students',
ComponentInstance.withDefaults(
id: 's-students',
componentKey: 'lms_stat_card',
registry: registry,
configOverrides: {'title': 'Total Students', 'value': 1247},
),
flex: 2,
),
DashboardColumn.withComponent('col-courses',
ComponentInstance.withDefaults(
id: 's-courses',
componentKey: 'lms_stat_card',
registry: registry,
configOverrides: {'title': 'Active Courses', 'value': 42},
),
flex: 2,
),
DashboardColumn.withComponent('col-rate',
ComponentInstance.withDefaults(
id: 's-rate',
componentKey: 'lms_stat_card',
registry: registry,
configOverrides: {'title': 'Completion Rate', 'value': 87},
),
flex: 2,
),
],
),
// Row 2: Charts (2:1 ratio)
DashboardRow(
id: 'row-charts',
order: 1,
title: 'Trends',
height: 300,
columns: [
DashboardColumn.withComponent('col-trend',
ComponentInstance.withDefaults(
id: 's-trend',
componentKey: 'lms_enrollment_chart',
registry: registry,
configOverrides: {'chartType': 'line'},
),
flex: 4,
),
DashboardColumn.withComponent('col-dist',
ComponentInstance.withDefaults(
id: 's-dist',
componentKey: 'lms_course_distribution',
registry: registry,
configOverrides: {'chartType': 'pie'},
),
flex: 2,
),
],
),
// Row 3: Full-width detail table
DashboardRow(
id: 'row-details',
order: 2,
title: 'Recent Completions',
height: 250,
columns: [
DashboardColumn.withComponent('col-table',
ComponentInstance.withDefaults(
id: 's-table',
componentKey: 'lms_completions_table',
registry: registry,
),
flex: 6,
),
],
),
],
);Next Steps
- Component System -- Define custom components with ComponentDescriptor
- Data Sources -- Connect components to API data
- Building Dashboards -- Step-by-step guide