LLM-friendly URL

Building your own dashboard studio

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:

  1. Set up your visualization canvas
    Choose between a single chart or a multi-chart dashboard layout where your users' visualizations will appear.

  2. Add data selection controls
    Let users pick which data fields to visualize and assign them to chart slots like axes, measures, and legends.

  3. Enable filtering capabilities
    Give users the power to focus on specific data subsets by applying filters to their charts.

  4. 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).

Example implementation

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:

Item gridluzmo-item-grid
Hover over the chart and use the action buttons to edit data, options, or filters.
ℹ️

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.).


Installation

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.

Command Line
Web component
Angular
React
React Native
Vue
npm install @luzmo/analytics-components-kit
# Optional (individual chart embedding/editing): npm install @luzmo/embed
# Optional (chart icons): npm install @luzmo/icons

Import 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:

index.js
Web component
Angular
React
React Native
Vue
// 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';

Visualizing dashboard items

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:

  1. 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.

  2. 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).

html
<!-- 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: {...} }, ...]} />

Single chart with Flex item

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 ).

Chart type
index.html
Web component
Angular
React
React Native
Vue
<!-- 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>

Multiple dashboard items in a grid

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.

View mode
Actions menu
Theme
index.html
Web component
Angular
React
React Native
Vue
<!-- 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).

html
<!-- 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:

javascript
// 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 .

Switching between chart types

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.

Chart 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.

index.html
Web component
Angular
React
React Native
Vue
<!-- 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>

Chart data

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.

Field list & Slot drops

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 ).

Size
Data field panel
Search
Search threshold
Search placeholder
Dataset picker
Slot drop panel
Chart type
Grows
Hide configuration
Hide delete
index.html
Web component
Angular
React
React Native
Vue
<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 .

Slot dropdowns

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.

Chart type
Size
Grows
Single selection
index.html
Web component
Angular
React
React Native
Vue
<!-- 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.

html
<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.

html
<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.

html
<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.

Filters

ℹ️

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).

Size
Language
Timezone
index.html
Web component
Angular
React
React Native
Vue
<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 .

html
<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 .


Item options

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 ).

Chart options<luzmo-item-option-panel>
Luzmo visualization item<luzmo-viz-item>
PANEL MODE
CHART TYPE
LANGUAGE
index.html
Web component
Angular
React
React Native
Vue
<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 .


Theming

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.

Default
Standard Luzmo theme
Select a theme preset:
CSS Variables
.my-editor-theme {
  /* Use default Luzmo theme */
}

How it works

Wrap your editor components in a container and set CSS variables to customize the look:

html
<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>
css
.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.

Common theme variables

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 ).


Persisting a chart configuration

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.


Next steps

Now that you have a working dashboard studio, consider exploring these resources:

Did this page help you?
Yes No