In this guide, we'll walk you through building a custom dashboard studio using Luzmo's Analytics Components Kit (ACK) . It is a suite of components that let you easily create chart/dashboard-configuration UIs, including data field panels, slot editors, item option panels, filter builders, and grid layouts. This allows you to provide a dashboard studio that's fully customized to your needs and seamlessly integrated into your application, with minimal effort required to build and maintain . You control the full editor experience, including the look & feel, the layout, and the functionality - Luzmo's ACK components handle all complexity behind the scenes .
Why build a custom dashboard studio? Give your users the power to create and edit their own charts and dashboards without leaving your application. Instead of pre-building every report variation, let users choose which data to visualize, apply filters, and customize how charts look, all within an interface that matches your brand and workflow.
The key advantage: You're in control. Pick and choose which components and functionality to provide based on your users' needs. For simple use cases, offer a streamlined editor with just the essentials. For power users, provide the full suite with multi-chart dashboards, drag-and-drop layouts, and advanced customization options. You can even tailor different editing experiences for different user roles or features within your application.
How you'll build it:
Set up your visualization canvas
Choose between a single chart or a multi-chart dashboard layout where your users' visualizations will appear.
Add data selection controls
Let users pick which data fields to visualize and assign them to chart slots like axes, measures, and legends.
Enable filtering capabilities
Give users the power to focus on specific data subsets by applying filters to their charts.
Provide customization options
Allow users to adjust the visual appearance of a visualization item through settings like colors, sorting, and other item-type-specific options.
Prerequisites: Make sure the "Analytics Components Kit" addon is included in your Luzmo license . Reach out to support@luzmo.com if you have questions about your license.
You may need to set appServer and apiHost to your Luzmo tenancy or VPC (defaults to EU VPC: https://app.luzmo.com and https://api.luzmo.com ). The authKey and authToken properties are omitted in the examples below for brevity - you should request the Embed Authorization key-token pair server-side and pass them to the frontend to load the components (see Generating an authorization token for more details). You will also need a valid Embed key and token with "can use" access to the datasets you want to query ("can use" is the minimum access level for a dataset — it allows querying data but not modifying the dataset schema).
Before diving into the details, here's what you can build by combining all the components covered in this guide — a complete dashboard studio where users can create, configure, and arrange multiple charts.
This preview shows a fully-featured implementation built by combining all the components covered in this guide. In this layout, the left side shows a dataset-scoped data field panel, the top area contains slot drop targets for chart mapping, and the center uses luzmo-item-grid so users can arrange and resize dashboard items. Each item's action buttons can open focused configuration panels such as:
Data mapping controls with luzmo-data-field-panel and luzmo-item-slot-drop-panel
Alternatively, use luzmo-item-slot-picker-panel for more compact or guided flows
Filter controls (including AND/OR logic) with luzmo-filters
Item settings with luzmo-item-option-panel
Looking for an out-of-the-box solution? If you want a fully-featured dashboard studio without building it component by component, check out the Embedded dashboard studio guide. It lets you embed Luzmo's own editor UI directly into your application with just a few lines of code. The component-by-component approach in this guide is the right choice when you need a custom, deeply integrated editing experience (e.g. minimalistic, different flow for chart creations, etc.).
Install @luzmo/analytics-components-kit to build the dashboard studio UI (slot editors, filters, item options, grids, etc.).
The embed package is optional: if you're building a multi-chart dashboard using luzmo-item-grid (see Multiple dashboard items in a grid ), the ACK package is all you need. If you also want to render a standalone single chart using luzmo-embed-viz-item (see Single chart with Flex item ), add the framework-specific embed package ( @luzmo/embed , @luzmo/react-embed , and similar).
Optionally, add @luzmo/icons if you want to render chart-type icons in pickers or button grids.
Each component can be imported individually from its own subpath (e.g. @luzmo/analytics-components-kit/data-field-panel ) for optimal bundle size, or you can import everything at once from the package root ( @luzmo/analytics-components-kit ). The examples below use individual imports, but you can replace them with a single top-level import if you prefer convenience over tree-shaking.
npm install @luzmo/analytics-components-kit
# Optional (individual chart embedding/editing): npm install @luzmo/embed
# Optional (chart icons): npm install @luzmo/iconsImport the components you need in a JavaScript module. You can import each component individually (recommended for production) or use a single top-level import to register all components at once:
// ACK editor components (individual imports for optimal bundle size)
import '@luzmo/analytics-components-kit/data-field-panel';
import '@luzmo/analytics-components-kit/item-slot-drop';
import '@luzmo/analytics-components-kit/item-slot-picker';
import '@luzmo/analytics-components-kit/item-slot-drop-panel';
import '@luzmo/analytics-components-kit/item-slot-picker-panel';
import '@luzmo/analytics-components-kit/filters';
import '@luzmo/analytics-components-kit/item-grid';
import '@luzmo/analytics-components-kit/item-option-panel';
// Or replace the above with a single import to register all ACK components:
// import '@luzmo/analytics-components-kit';
// Embed library - provides the <luzmo-embed-viz-item> chart component
import '@luzmo/embed';At the center of every dashboard studio is the visualization layer, which is how you render charts for users to edit. You have two options depending on your use case:
Flex item ( luzmo-embed-viz-item ): Use this to render a single chart that users can configure with ACK components (slots, options, filters). The Flex item re-renders automatically when its configuration changes.
Item Grid ( luzmo-item-grid ): Use this to render one or more items with built-in layout capabilities. Users can arrange, resize, and manage multiple dashboard items on a grid. Each grid item has an action menu that you can use to trigger configuration panels (slots, options, filters).
<!-- Option 1: Flex item — renders a single editable chart. -->
<!-- Installed & imported from @luzmo/embed (or framework-specific wrappers @luzmo/react-embed, @luzmo/ngx-embed, or @luzmo/vue-embed) -->
<!--
type: item type identifier (e.g. 'column-chart', 'line-chart');
slots: what data to show (measures, dimensions);
options: how it looks;
filters: which rows to include.
-->
<luzmo-embed-viz-item type="column-chart" slots={...} options={...} filters={...} />
<!-- Framework-specific wrappers use <luzmo-viz-item> or <LuzmoVizItemComponent> components instead of <luzmo-embed-viz-item> -->
<!-- Option 2: Item Grid — renders multiple items with drag-and-resize layout. -->
<!-- Each item has its own type, slots, and position (col, row, sizeX, sizeY) on the grid. -->
<luzmo-grid items={[{ type: 'column-chart', slots: [...], position: {...} }, ...]} /> To edit a single visualization item, you can use the Flex SDK ( luzmo-embed-viz-item ). Below you can see a minimal Flex item that currently has hardcoded slots & options. In the sections that follow, we will replace this hardcoded config with editor components so users can change it interactively (see Chart data , Filters , and Item options ).
<!-- luzmo-embed-viz-item: renders a single Flex chart.
type: the chart type identifier (e.g. 'column-chart', 'line-chart', 'pie-chart')
appServer/apiHost: configured via environment variables
authKey/authToken: passed as arguments, fetched server-side before loading -->
<luzmo-embed-viz-item
id="vizItem"
type="column-chart"
></luzmo-embed-viz-item>
<script type="module">
import '@luzmo/embed';
// Fetch Embed Authorization token from your backend before initializing the component.
// The token should provide "can use" access to the datasets referenced below.
// See: https://developer.luzmo.com//guide/dashboard-embedding--generating-an-authorization-token
async function initializeChart() {
const { authKey, authToken } = await fetch('/api/embed-token').then(r => r.json());
const vizItem = document.getElementById('vizItem');
// Configure API endpoints from environment variables (e.g., VITE_LUZMO_API_HOST)
vizItem.appServer = import.meta.env.VITE_LUZMO_APP_SERVER || 'https://app.luzmo.com';
vizItem.apiHost = import.meta.env.VITE_LUZMO_API_HOST || 'https://api.luzmo.com';
// Set authorization credentials from your backend
vizItem.authKey = authKey;
vizItem.authToken = authToken;
// slots: define what data the chart displays. Each slot maps a named chart dimension
// (e.g. 'measure', 'x-axis', 'legend') to one or more dataset column references.
vizItem.slots = [
{
name: 'measure', // Slot name — determines the chart dimension (y-axis for column chart)
content: [{
label: { en: 'Revenue' },
datasetId: 'your-dataset-id', // ID of the Luzmo dataset
columnId: 'your-column-id', // ID of the column within that dataset
type: 'numeric', // Column data type: 'numeric', 'hierarchy', or 'datetime'
aggregationFunc: 'sum' // Aggregation: 'sum', 'count', 'avg', 'min', 'max', etc.
}]
}
];
// options: chart-type-specific display settings (colors, sorting, labels, axes, etc.)
vizItem.options = {
display: {
title: false
}
};
// filters: array of filter groups that narrow which data rows are queried (see Filters section)
vizItem.filters = [];
}
initializeChart();
</script> The luzmo-item-grid component lets users arrange, resize, and manage multiple items on a grid. You pass item definitions (type, slots, options, position - just like the Flex item component) and the grid handles the rest.
Built-in item actions include editing, cloning, and deleting items. Listen to the luzmo-item-grid-item-action event to respond to these actions in your application (for example, to open a configuration panel when the user clicks "edit"). You can also listen to the luzmo-item-grid-changed event to keep track of changes in the grid layout.
<!-- luzmo-grid: renders multiple items in a drag-and-resize grid layout.
columns: number of grid columns (higher = more precise positioning)
row-height: height in px of each grid row
view-mode: (optional) disables drag, resize, and item actions for read-only dashboards
placement-item-actions-menu: (optional) controls actions menu position (e.g., "right-start", "top-end")
auth credentials and API endpoints are set via JavaScript -->
<luzmo-grid
id="grid"
language="en"
columns="48"
row-height="16"
></luzmo-grid>
<!-- Example with view-mode enabled for read-only dashboards: -->
<!-- <luzmo-grid id="grid" language="en" columns="48" row-height="16" view-mode></luzmo-grid> -->
<!-- Example with custom action menu placement: -->
<!-- <luzmo-grid id="grid" language="en" columns="48" row-height="16" placement-item-actions-menu="right-start"></luzmo-grid> -->
<script type="module">
import '@luzmo/analytics-components-kit/item-grid';
// Fetch Embed Authorization token from your backend before initializing the grid.
// The token should provide "can use" access to all datasets referenced in grid items.
// See: https://developer.luzmo.com//guide/dashboard-embedding--generating-an-authorization-token
async function initializeGrid() {
const { authKey, authToken } = await fetch('/api/embed-token').then(r => r.json());
const grid = document.getElementById('grid');
// Configure API endpoints from environment variables (e.g., VITE_LUZMO_API_HOST)
grid.appServer = import.meta.env.VITE_LUZMO_APP_SERVER || 'https://app.luzmo.com';
grid.apiHost = import.meta.env.VITE_LUZMO_API_HOST || 'https://api.luzmo.com';
grid.authKey = authKey;
grid.authToken = authToken;
// items: array of chart definitions. Each item has its own type, slots, options,
// filters, and position within the grid.
grid.items = [
{
id: 'chart-1', // Unique identifier for this grid item
type: 'column-chart', // Chart type identifier
options: {}, // Chart-type-specific display settings
filters: [], // Filter groups applied to this chart
slots: [
{
name: 'measure', // Slot name — determines the chart dimension
content: [{
label: { en: 'Revenue' },
columnId: 'your-column-id',
datasetId: 'your-dataset-id',
type: 'numeric',
aggregationFunc: 'sum'
}]
}
],
// position: grid placement. col/row are 0-based; sizeX/sizeY are in grid units.
// With columns=48, sizeX=24 means the item spans half the grid width.
position: { col: 0, row: 0, sizeX: 24, sizeY: 20 }
},
{
id: 'chart-2',
type: 'line-chart',
options: {},
filters: [],
slots: [
{
name: 'measure',
content: [{
label: { en: 'Sales' },
columnId: 'your-other-column-id',
datasetId: 'your-dataset-id',
type: 'numeric',
aggregationFunc: 'sum'
}]
}
],
position: { col: 24, row: 0, sizeX: 24, sizeY: 20 }
}
];
// Fired when users drag or resize items. e.detail.items contains the full
// updated items array with new positions — persist this to save the layout.
grid.addEventListener('luzmo-item-grid-changed', (e) => {
console.log('Grid layout changed:', e.detail.items);
});
// Fired when a user clicks an action button on a grid item.
// action: 'edit' | 'clone' | 'delete' — use 'edit' to open a config panel.
// id: the item's id; items: the full items array after the action.
grid.addEventListener('luzmo-item-grid-item-action', (e) => {
const { action, id, items } = e.detail;
console.log(`Action "${action}" on item "${id}"`);
});
// Optional: Apply a custom theme to style the grid and its items.
// See the "Theme" section below for the full customTheme object structure.
// grid.theme = customTheme;
}
initializeGrid();
</script>Theme : Pass a theme configuration to style the grid and its dashboard items. Use your own custom theme configured to your application's look & feel (if you created a theme in your Luzmo organization, you can retrieve it via the Theme API endpoint).
<!-- Use a custom theme stored in your Luzmo organization -->
<luzmo-grid theme='{"id": "your-custom-theme-id"}'></luzmo-grid>
<!-- Or pass a full custom theme object -->
<luzmo-grid theme='{...your-custom-theme-object}'></luzmo-grid>A full theme object looks like this:
// Full custom theme object — gives you complete control over chart styling.
// Pass this to luzmo-grid's theme property or to individual luzmo-embed-viz-item components.
const customTheme = {
type: 'custom',
font: { // Global font settings for all chart text
fontFamily: 'Inter',
fontSize: 12
},
background: 'rgb(250,250,252)', // Grid canvas background
itemsBackground: 'rgba(255,255,255,1)',// Individual chart card background
editBackground: 'rgb(245,245,248)', // Background when a chart is in edit mode
editDarkOrLight: 'light-edit', // 'light-edit' or 'dark-edit' — controls edit UI contrast
mainColor: 'rgb(99,91,255)', // Primary accent color (selections, highlights)
colors: [ // Color palette used for chart series/segments
'rgb(99,91,255)',
'rgb(56,189,248)',
'rgb(52,211,153)',
'rgb(251,146,60)',
'rgb(244,114,182)',
'rgb(139,92,246)',
'rgb(45,212,191)',
'rgb(234,179,8)',
'rgb(248,113,113)',
'rgb(96,165,250)',
'rgb(163,230,53)',
'rgb(232,121,249)'
],
title: { // Chart title styling
bold: true,
align: 'left',
border: false,
italic: false,
underline: false,
fontSize: 14,
lineHeight: 20
},
borders: { // Chart card border styling
'border-color': 'rgb(229,231,235)',
'border-style': 'solid',
'border-radius': '12px',
'border-top-width': '1px',
'border-right-width': '1px',
'border-bottom-width': '1px',
'border-left-width': '1px'
},
margins: [12, 12], // [horizontal, vertical] padding inside each chart card
boxShadow: { // Card shadow — size: 'none' | 'S' | 'M' | 'L'
size: 'S',
color: 'rgba(0,0,0,0.04)'
},
axis: { fontSize: 11 }, // Axis tick label font size
legend: { // Legend styling
type: 'circle', // Swatch shape: 'circle' | 'square' | 'line'
fontSize: 11,
lineHeight: 16
},
tooltip: { // Tooltip popup styling
background: 'rgb(15,23,42)',
fontSize: 12
}
}; For full details on grid properties, events, and CSS variables, see the luzmo-item-grid reference .
Once users understand the basics of rendering charts, you may want to let them dynamically switch between different visualization types (e.g. column chart → line chart → pie chart). The Analytics Components Kit provides itemTypes for discovering available visualization types, and switchItem for migrating slot data and options when switching between types.
How item type switching works: When you switch from one item type to another, switchItem automatically transfers your data fields to the new chart by matching compatible field types (like numeric values or categories) to the appropriate visualization slots, preserving as much of your configuration as possible. The slots returned by switchItem will adhere to the slot configuration of the new item type.
<!-- Chart type selector grid -->
<div id="chartTypeGrid" style="display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 8px;"></div>
<!-- luzmo-embed-viz-item renders a single chart; it re-renders automatically when
type, slots, options, or filters change. -->
<luzmo-embed-viz-item
id="vizItem"
type="column-chart"
></luzmo-embed-viz-item>
<script type="module">
// itemTypes: list of all available item types (column-chart, line-chart, pie-chart, etc.)
import { itemTypes } from '@luzmo/analytics-components-kit/item-list';
// switchItem: migrates slot data + options when switching between item types
import { switchItem } from '@luzmo/analytics-components-kit/utils';
import '@luzmo/embed';
// Fetch Embed Authorization token from your backend before initializing the chart.
// The token should provide access to the datasets referenced in the chart.
// See: https://developer.luzmo.com/guide/dashboard-embedding--generating-an-authorization-token
async function initializeChartEditor() {
const { authKey, authToken } = await fetch('/api/embed-token').then(r => r.json());
const chartTypeGrid = document.getElementById('chartTypeGrid');
const vizItem = document.getElementById('vizItem');
// Configure API endpoints from environment variables (e.g., VITE_LUZMO_API_HOST)
vizItem.appServer = import.meta.env.VITE_LUZMO_APP_SERVER || 'https://app.luzmo.com';
vizItem.apiHost = import.meta.env.VITE_LUZMO_API_HOST || 'https://api.luzmo.com';
vizItem.authKey = authKey;
vizItem.authToken = authToken;
// Filter to only item types that visualize data (excludes decorative items like text boxes)
const chartOptions = itemTypes
.filter(item => item.containsData)
.map(item => ({
label: item.label, // Human-readable name, e.g. "Column chart"
value: item.type // Identifier used in type prop, e.g. "column-chart"
}));
let currentType = 'column-chart';
// slots: define what data the chart displays. Each slot has a name (e.g. 'measure',
// 'x-axis', 'legend') and content array of column references.
let currentSlots = [
{
name: 'measure', // Slot name — determines the chart dimension (y-axis for column chart)
content: [{
label: { en: 'Revenue' },
datasetId: 'your-dataset-id', // ID of the Luzmo dataset
columnId: 'your-column-id', // ID of the column within that dataset
type: 'numeric', // Column data type: 'numeric', 'hierarchy', or 'datetime'
aggregationFunc: 'sum' // Aggregation: 'sum', 'count', 'avg', 'min', 'max', etc.
}]
}
];
// options: chart-type-specific display settings (colors, sorting, labels, axes, etc.)
let currentOptions = {};
vizItem.slots = currentSlots;
vizItem.options = currentOptions;
const applyChartType = async (nextType) => {
const previousType = currentType;
// switchItem redistributes slot contents to compatible slots in the new chart type.
// Numeric fields go to numeric/mixed slots; categorical fields go to categorical/mixed slots.
const result = await switchItem({
oldItemType: previousType,
newItemType: nextType,
slots: currentSlots,
options: currentOptions
});
currentType = nextType;
currentSlots = result.slots; // Migrated slots for the new chart type
currentOptions = result.options || {};
vizItem.setAttribute('type', currentType);
vizItem.slots = currentSlots;
vizItem.options = currentOptions;
// Tip: feed currentSlots into luzmo-item-slot-drop-panel or
// luzmo-item-slot-picker-panel, and currentOptions into luzmo-item-option-panel
// to keep editor panels in sync after switching.
};
chartOptions.forEach((option) => {
const button = document.createElement('button');
button.type = 'button';
button.style.display = 'flex';
button.style.gap = '6px';
button.style.alignItems = 'center';
button.style.padding = '6px 8px';
button.textContent = option.label;
button.addEventListener('click', () => void applyChartType(option.value));
chartTypeGrid.appendChild(button);
});
}
initializeChartEditor();
</script>Charts need to know which data to display. In Luzmo, you configure this using slots — named placeholders on a chart (like "X axis", "Measure", or "Legend") that you fill with columns from your dataset. Think of slots as the chart's input fields: a column chart typically has a "measure" slot (what to count or sum) and an "x-axis" slot (how to group the data).
The slots of a chart determine which columns (and/or formulas) are used in the visualization items to query the insights (e.g. total sales by country and region, minimum of order value by customer age bins, etc.). Each visualization type has specific slots designed to easily configure the data visualized in that chart. To fill these slots, you can use a list with draggable fields and slot targets that allow dropping fields into them (see Field list & Slot drops ). An alternative approach could be to use a dropdown on each slot , which could be useful when e.g. space is limited (see Slot dropdowns ).
Choosing between drag-and-drop and pickers: To provide a drag-and-drop experience to your end-users, use the luzmo-data-field-panel and luzmo-item-slot-drop-panel components, where users drag from the field list into each slot. For more compact layouts (e.g. mobile screens), you can use the luzmo-item-slot-picker-panel to provide dropdowns on the slots instead.
A data field represents a column or formula from a dataset. The luzmo-data-field-panel component renders a browsable list of draggable fields for one or more datasets, with built-in search and optional dataset switching. These can be used to populate the slots of a chart using drag-and-drop.
Every chart type is defined by one or more slots (e.g. "measure", "x-axis", "legend", etc.). Users can assign data fields to these slots using drag-and-drop to configure what the chart shows. The luzmo-item-slot-drop-panel renders all drag-and-drop slot targets for a given chart type.
You can either pass dataset IDs and let the component fetch fields via Luzmo's API, or provide the dataset metadata directly (e.g. retrieved from the Dataset API ).
<div style="display: flex; gap: 16px;">
<!-- luzmo-data-field-panel: browsable list of all dataset columns.
Users drag fields from here into slot drop targets.
dataset-picker: shows a dataset switcher when multiple datasets are provided.
search="auto": shows search input when column count exceeds threshold (default 20).
search-threshold: customize when search appears (e.g., 10 columns instead of 20). -->
<luzmo-data-field-panel
id="fieldsPanel"
language="en"
size="m"
dataset-picker
search="auto"
search-threshold="10"
></luzmo-data-field-panel>
<!-- luzmo-item-slot-drop-panel: renders all drop targets for a given chart type.
item-type: must match the chart's type so the correct slots are shown
(e.g. column-chart has 'measure', 'x-axis', 'legend' slots). -->
<luzmo-item-slot-drop-panel
id="dropPanel"
item-type="column-chart"
language="en"
size="m"
></luzmo-item-slot-drop-panel>
<!-- The chart that visualizes the current slot configuration -->
<luzmo-embed-viz-item
id="vizItem"
type="column-chart"
></luzmo-embed-viz-item>
</div>
<script type="module">
import '@luzmo/analytics-components-kit/data-field-panel';
import '@luzmo/analytics-components-kit/item-slot-drop-panel';
import '@luzmo/embed';
// Fetch Embed Authorization token from your backend before initializing editor components.
// The token should provide access to all datasets referenced in the editor.
// See: https://developer.luzmo.com/guide/dashboard-embedding--generating-an-authorization-token
async function initializeSlotEditor() {
const { authKey, authToken } = await fetch('/api/embed-token').then(r => r.json());
const fieldsPanel = document.getElementById('fieldsPanel');
const dropPanel = document.getElementById('dropPanel');
const vizItem = document.getElementById('vizItem');
// Configure API endpoints from environment variables (e.g., VITE_LUZMO_API_HOST)
const apiHost = import.meta.env.VITE_LUZMO_API_HOST || 'https://api.luzmo.com';
const appServer = import.meta.env.VITE_LUZMO_APP_SERVER || 'https://app.luzmo.com';
// Set API configuration for all components
fieldsPanel.apiUrl = apiHost;
fieldsPanel.authKey = authKey;
fieldsPanel.authToken = authToken;
dropPanel.apiUrl = apiHost;
dropPanel.authKey = authKey;
dropPanel.authToken = authToken;
vizItem.appServer = appServer;
vizItem.apiHost = apiHost;
vizItem.authKey = authKey;
vizItem.authToken = authToken;
// datasetIds: which datasets to show fields for in the panel.
// The component fetches column metadata from the API automatically.
fieldsPanel.datasetIds = ['your-dataset-id'];
// slotsContents: shared state between the drop panel and the chart.
// Same format as vizItem.slots — array of { name, content } objects.
let slotsContents = [];
let options = {};
let filters = [];
dropPanel.slotsContents = slotsContents;
vizItem.options = options;
vizItem.filters = filters;
// Fired when a user drops a field into a slot, removes a field, or reorders fields.
// e.detail.slotsContents: the full updated slots array — pass it to both the
// drop panel (to keep its UI in sync) and the chart (to re-render).
dropPanel.addEventListener('luzmo-slots-contents-changed', (e) => {
slotsContents = e.detail.slotsContents;
dropPanel.slotsContents = slotsContents;
vizItem.slots = slotsContents;
});
}
initializeSlotEditor();
</script> For full details on all properties, events, and CSS variables, see the luzmo-data-field-panel reference . If you need precise control over which fields appear, you can render individual luzmo-data-field elements instead.
For full details around the slot panel, see the luzmo-item-slot-drop-panel reference . If you need more control over the individual slots, you can use the luzmo-item-slot-drop reference .
Instead of using drag-and-drop, you can use the dropdown-based luzmo-item-slot-picker-panel component (e.g. for compact forms or mobile layouts) instead of pairing luzmo-data-field-panel with slot drop targets . Users pick fields from a dropdown inside each slot rather than dragging from a separate field list. The panel automatically loads slot configurations and enforces constraints.
<!-- luzmo-item-slot-picker-panel: dropdown-based alternative to drag-and-drop.
Users pick fields from a dropdown inside each slot.
item-type: must match the chart's type so the correct slots are shown.
dataset-picker: adds a dataset switcher dropdown above the slot pickers. -->
<luzmo-item-slot-picker-panel
id="pickerPanel"
item-type="column-chart"
language="en"
size="m"
dataset-picker
></luzmo-item-slot-picker-panel>
<luzmo-embed-viz-item
id="vizItem"
type="column-chart"
></luzmo-embed-viz-item>
<script type="module">
import '@luzmo/analytics-components-kit/item-slot-picker-panel';
import '@luzmo/embed';
// Fetch Embed Authorization token from your backend before initializing picker panel.
// The token should provide access to all datasets used in the picker.
// See: https://developer.luzmo.com//guide/dashboard-embedding--generating-an-authorization-token
async function initializePicker() {
const { authKey, authToken } = await fetch('/api/embed-token').then(r => r.json());
const pickerPanel = document.getElementById('pickerPanel');
const vizItem = document.getElementById('vizItem');
// Configure API endpoints from environment variables (e.g., VITE_LUZMO_API_HOST)
const appServer = import.meta.env.VITE_LUZMO_APP_SERVER || 'https://app.luzmo.com';
const apiHost = import.meta.env.VITE_LUZMO_API_HOST || 'https://api.luzmo.com';
// Set API configuration for all components
pickerPanel.apiUrl = apiHost;
pickerPanel.authKey = authKey;
pickerPanel.authToken = authToken;
vizItem.appServer = appServer;
vizItem.apiHost = apiHost;
vizItem.authKey = authKey;
vizItem.authToken = authToken;
// datasetIds: which datasets to show fields for in the picker.
// The component fetches column metadata from the API automatically.
pickerPanel.datasetIds = ['your-dataset-id'];
// slotsContents: shared state between the picker panel and the chart.
// Same format as vizItem.slots — array of { name, content } objects.
let slotsContents = [];
let options = {};
let filters = [];
pickerPanel.slotsContents = slotsContents;
vizItem.options = options;
vizItem.filters = filters;
// Fired when a user selects or removes a field from any slot dropdown.
// e.detail.slotsContents: the full updated slots array — pass it to both the
// picker panel (to keep its UI in sync) and the chart (to re-render).
pickerPanel.addEventListener('luzmo-slots-contents-changed', (e) => {
slotsContents = e.detail.slotsContents;
pickerPanel.slotsContents = slotsContents;
vizItem.slots = slotsContents;
});
}
initializePicker();
</script> Selection mode : Use selects="single" to restrict each slot dropdown to one field selected (if the slot supports multiple fields, a "+" icon button will appear below the dropdown), or selects="multiple" (default) to allow multiple selected fields in a slot dropdown.
<luzmo-item-slot-picker-panel
selects="single"
></luzmo-item-slot-picker-panel>Grows : Makes the picker stretch to fill the available width, which is useful in form-style layouts.
<luzmo-item-slot-picker-panel
grows
></luzmo-item-slot-picker-panel>Dataset picker : Adds a dataset switcher dropdown above the slot pickers when working with multiple datasets.
<luzmo-item-slot-picker-panel
dataset-picker
></luzmo-item-slot-picker-panel> For full details, see the luzmo-item-slot-picker-panel reference and the luzmo-item-slot-picker reference for individual picker control.
Slots vs. Filters: These are two separate concepts that work together. Slots define which columns are queried (e.g. "show revenue by country"). Filters define which rows are included (e.g. "only the last 30 days, only orders from Europe"). Both are passed separately to the chart.
Filters narrow which rows from your datasets are queried by the chart. The slots define which columns (and/or formulas) are used in the chart (e.g. average revenue by quarter and customer type), the filters define which rows should be included in the chart (e.g. filtered to last 30 days, only orders from Europe).
The luzmo-filters component (used via the <luzmo-edit-filters> tag) lets users build and edit filter rules with AND/OR logic and nested groups. You can pass the output of the filters component directly to a Flex item's filters prop (or the filters property of a luzmo-item-grid item).
<luzmo-edit-filters
id="editFilters"
language="en"
size="m"
></luzmo-edit-filters>
<luzmo-embed-viz-item
id="vizItem"
type="column-chart"
></luzmo-embed-viz-item>
<script type="module">
import '@luzmo/analytics-components-kit/filters';
import '@luzmo/embed';
async function initializeFilters() {
const { authKey, authToken } = await fetch('/api/embed-token').then(r => r.json());
const editFilters = document.getElementById('editFilters');
const vizItem = document.getElementById('vizItem');
const apiHost = import.meta.env.VITE_LUZMO_API_HOST || 'https://api.luzmo.com';
const appServer = import.meta.env.VITE_LUZMO_APP_SERVER || 'https://app.luzmo.com';
editFilters.apiUrl = apiHost;
editFilters.authKey = authKey;
editFilters.authToken = authToken;
vizItem.appServer = appServer;
vizItem.apiHost = apiHost;
vizItem.authKey = authKey;
vizItem.authToken = authToken;
editFilters.datasetIds = ['your-dataset-id'];
editFilters.filters = [
{
condition: 'and',
filters: [
{
expression: '? > ?',
parameters: [
{ columnId: 'volume-column-id', datasetId: 'your-dataset-id' },
100
]
},
{
expression: '? in ?',
parameters: [
{ columnId: 'product-column-id', datasetId: 'your-dataset-id' },
['Earthly Shoes', 'Luzmo Sneakers']
]
}
],
subGroups: [
{
condition: 'or',
filters: [
{
expression: '? in ?',
parameters: [
{ columnId: 'location-column-id', datasetId: 'your-dataset-id' },
['New York']
]
},
{
expression: '? in ?',
parameters: [
{ columnId: 'location-column-id', datasetId: 'your-dataset-id' },
['London']
]
}
],
subGroups: []
}
]
}
];
editFilters.addEventListener('luzmo-filters-changed', (e) => {
vizItem.filters = e.detail.filters;
});
}
initializeFilters();
</script> Debounce : Controls how long (in ms) the component waits after the last user interaction before firing luzmo-filters-changed . Default is 300 .
<luzmo-edit-filters debounce="500"></luzmo-edit-filters>
<luzmo-edit-filters debounce="0"></luzmo-edit-filters> For full details on filter properties, events, and CSS variables, see the luzmo-filters reference .
Once users have selected which data to display (via slots) and which rows to include (via filters), they'll want to customize how the chart looks. Options control the visual appearance and behavior — things like whether to show a legend, which color palette to use, whether bars are stacked or grouped, etc. Each chart type has its own set of available options.
Options control how the chart looks and behaves (e.g. colors, axes, labels, sorting, and other settings that depend on chart type). The luzmo-item-option-panel renders those controls for a given item-type and reads and writes the Flex item options object.
When a user changes a setting, the panel fires luzmo-options-changed with { detail: { options } } . Apply that object to your luzmo-embed-viz-item or luzmo-item-grid item the same way you apply slots and filters .
Choosing between full panel and individual controls: Use luzmo-item-option-panel to render all available options for a chart type at once. For custom layouts or to expose only specific settings, use individual luzmo-item-option components with an option-key (e.g. mode , display.legend , axis.xAxis.labels.rotation ).
<div style="display: flex; gap: 16px;">
<!-- luzmo-item-option-panel: renders all configuration controls for the chart type.
item-type: must match the chart's type so the correct options are shown.
options: current chart options object (kept in sync with the chart).
slots: current chart slots array (some options depend on which data is assigned). -->
<luzmo-item-option-panel
id="optionPanel"
item-type="column-chart"
language="en"
size="m"
></luzmo-item-option-panel>
<!-- For individual controls instead of the full panel, use luzmo-item-option:
<luzmo-item-option
id="modeOption"
item-type="column-chart"
option-key="mode"
language="en"
size="m"
></luzmo-item-option>
-->
<!-- The chart that visualizes the current option configuration -->
<luzmo-embed-viz-item
id="vizItem"
type="column-chart"
></luzmo-embed-viz-item>
</div>
<script type="module">
import '@luzmo/analytics-components-kit/item-option-panel';
// For individual controls, import: '@luzmo/analytics-components-kit/item-option'
import '@luzmo/embed';
async function initializeOptionEditor() {
const optionPanel = document.getElementById('optionPanel');
const vizItem = document.getElementById('vizItem');
const appServer = import.meta.env.VITE_LUZMO_APP_SERVER || 'https://app.luzmo.com';
const apiHost = import.meta.env.VITE_LUZMO_API_HOST || 'https://api.luzmo.com';
vizItem.appServer = appServer;
vizItem.apiHost = apiHost;
let slotsContents = [
{
name: 'measure',
content: [{
label: { en: 'Total Sales' },
columnId: 'sales-column-id',
datasetId: 'your-dataset-id',
type: 'numeric',
aggregationFunc: 'sum'
}]
},
{
name: 'x-axis',
content: [{
label: { en: 'Country' },
columnId: 'country-column-id',
datasetId: 'your-dataset-id',
type: 'hierarchy'
}]
}
];
let options = {
mode: 'grouped',
display: {
legend: true
}
};
let filters = [];
optionPanel.slots = slotsContents;
optionPanel.options = options;
vizItem.slots = slotsContents;
vizItem.options = options;
vizItem.filters = filters;
// Fired when a user changes any option setting.
// e.detail.options: the updated full options object — pass it to both the
// option panel (to keep its UI in sync) and the chart (to re-render).
optionPanel.addEventListener('luzmo-options-changed', (e) => {
options = e.detail.options;
optionPanel.options = options;
vizItem.options = options;
});
}
initializeOptionEditor();
</script> For full details on all properties, events, and CSS variables, see the luzmo-item-option-panel reference and luzmo-item-option reference .
Now that your dashboard studio is functionally complete, you'll likely want it to match your application's design system. Theming is optional but recommended to create a cohesive user experience — it ensures the editor components seamlessly integrate with your application.
All editor components expose CSS custom properties that let you customize their appearance to match your application's brand and design system. You can set variables on a parent container to theme all components at once, or target individual components for fine-grained control.
Try it out : Select a theme preset below to see how CSS variables transform the appearance of the editor components.
.my-editor-theme {
/* Use default Luzmo theme */
}Wrap your editor components in a container and set CSS variables to customize the look:
<div class="my-editor-theme">
<luzmo-data-field-panel ...></luzmo-data-field-panel>
<luzmo-item-slot-drop-panel ...></luzmo-item-slot-drop-panel>
<luzmo-edit-filters ...></luzmo-edit-filters>
</div>.my-editor-theme {
--luzmo-primary: #0052cc;
--luzmo-background-color: #f4f9fd;
--luzmo-border-radius: 8px;
--luzmo-item-slot-drop-filled-background-color: #e3f2fd;
--luzmo-filters-condition-badge-background: #1565c0;
}Because CSS variables inherit, child components automatically pick up the theme. You can also override variables on individual components for more control.
These variables affect all components and are the quickest way to align the editor with your brand:
--luzmo-primary — Primary brand color used for buttons and highlights
--luzmo-background-color — Base background color for panels and controls
--luzmo-font-family — Font stack applied to all text
--luzmo-border-radius — Corner rounding for all UI elements
--luzmo-border-color-rgb — RGB channels for borders (e.g. 30, 120, 200 )
Each component also exposes component-specific variables for detailed customization. For the complete list, see the Patterns page and each component's reference documentation (see "Analytics Components Kit" overview ).
Important: The ACK components are stateless — they don't automatically save or persist any configuration. You are responsible for saving the slots , options , filters , and grid items that are returned from component change events to a Luzmo dashboard or your own database. When a user returns to the editor, reload this saved state (e.g. from a Luzmo dashboard ) and pass it back to the components via their respective properties. When a user creates a new chart/dashboard, you can use the Luzmo dashboard API to create a new dashboard and pass the initial configuration to the components via their respective properties.
Now that you have a working dashboard studio, consider exploring these resources:
Generating an Embed Authorization Token : Securely provide end-users access to your datasets (and dashboards)
ACK Overview : Architecture and design principles
Component references:
Data Fields : luzmo-data-field , luzmo-data-field-panel
Item Data Slots : luzmo-item-slot-drop , luzmo-item-slot-drop-panel , luzmo-item-slot-picker , luzmo-item-slot-picker-panel
Item options : luzmo-item-option-panel , luzmo-item-option
Filters : luzmo-filters
Grid layout : luzmo-item-grid
Flex SDK Introduction : Installation and basic usage of Flex visualization items
Flex Chart Types and Options : All supported visualization item types and their configuration