UI components
Action Triggers and Custom Panels
The Felt SDK enables you to extend Felt maps with custom UI components that integrate seamlessly with the native interface. These extensions allow you to add interactive controls and custom workflows directly within the map experience.
UI Extension Points
Felt provides two primary ways to add custom UI to your maps:

Action Triggers appear as buttons in the left sidebar and provide quick access to custom actions. Think of them as shortcuts that users can click to trigger specific functionality in your application.
Custom Panels appear in the right sidebar and offer a full canvas for complex UI. These panels can contain forms, controls, and interactive elements that work together to create sophisticated user experiences.
Action Triggers
Action triggers are simple button controls that execute custom functions when clicked. They're perfect for actions that don't require additional user input - like applying filters, running calculations, or enabling an interaction mode.
await felt.createActionTrigger({
actionTrigger: {
label: "Check solar potential",
onTrigger: async () => {
// Enable polygon tool to allow a user to select a region
await felt.setTool("polygon");
// ...
},
}
});
Custom Panels
Custom panels provide a structured way to build complex UI within Felt. Each panel consists of three main sections that serve different purposes:
Panel Structure

Header - Contains the panel title, and an optional close button.
Body - Houses the main interactive elements like forms, selectors, and content areas. This is where users spend most of their time interacting with your custom functionality.
Footer - Typically contains primary action buttons like "Save", "Cancel", or "Apply". This creates a consistent pattern users expect from dialog-style interfaces. The footer sticks to the bottom of the panel, with a divider separating it from the body.
Getting Started with Panels
Create a panel by first generating an ID, then specifying its contents. You can control where panels appear using the initialPlacement
parameter. When onClickClose
is specified, a close button will be rendered in the header.
const panelId = await felt.createPanelId();
await felt.createOrUpdatePanel({
panel: {
id: panelId,
title: "Add report",
body: [
{ type: "Select", placeholder: "Choose a neighborhood", options: [...] },
{ type: "Select", placeholder: "Choose a severity", options: [...] },
{ type: "TextInput", placeholder: "Email", onBlur: storeEmail },
],
footer: [
{
type: "ButtonRow",
align: "end",
items: [
{ type: "Button", label: "Back", variant: "transparent", tint: "default", onClick: handleBack },
{ type: "Button", label: "Done", variant: "filled", tint: "primary", onClick: handleDone }
]
}
]
onClickClose: async () => {
// Clean up
await felt.deletePanel(panelId);
}
},
initialPlacement: { at: "start" } // Optional: control panel positioning
});
Panel Elements
Custom panels support a variety of interactive and display elements that can be combined to create rich user experiences:

Text Elements
Text elements display formatted content and support full Markdown rendering, allowing you to include headings, lists, links, and formatting within your panels.
{
type: "Text",
content: "**Welcome!** This is a *formatted* text element with [links](https://felt.com).",
}
TextInput Elements
TextInput elements allow users to enter custom values like names, descriptions, or numeric parameters.
{
type: "TextInput",
placeholder: "First name",
value: "",
onChange: (args) => {
console.log("New value:", args.value);
},
}
Control Elements
Control elements allow users to choose from predefined options:

Available control elements include Select, CheckboxGroup, RadioGroup, and ToggleGroup. Each element supports similar properties:
// Select dropdown
{
type: "Select", // or "CheckboxGroup" | "RadioGroup" | "ToggleGroup"
label: "Year",
options: [
{ value: "2025", label: "2025" },
{ value: "2024", label: "2024" },
],
value: "",
onChange: (args) => {
console.log("Selected:", args.value);
}
}
Button Elements
Button elements trigger actions and come in different styles to communicate their importance and effect. Buttons can have different variants (filled, outlined, and transparent) and tints (primary, accent, danger and default):

Primary filled buttons highlight the most important action in a context. Use sparingly - typically one per panel section.
{
type: "Button",
label: "Submit",
variant: "filled", // "transparent" | "outlined"
tint: "primary", // "default" | "accent" | "danger"
onClick: async () => {
// Handle button click
}
}
Button Rows
Group related buttons together to create clear action hierarchies:

Button rows automatically handle spacing and alignment, ensuring your panels look polished and consistent.
{
type: "ButtonRow",
align: "end",
items: [
{ type: "Button", label: "Clear", variant: "transparent", tint: "default", onClick: handleClear },
{ type: "Button", label: "Send report", variant: "filled", tint: "primary", onClick: handleSend }
]
}
Grid Elements
The grid element helps organize elements within panels to create complex layouts. It uses a grid
property that follows the same syntax as the CSS shorthand grid property, and includes verticalAlignment
and horizontalDistribution
properties for precise control over layout positioning.

{
type: "Grid",
grid: "auto-flow / 2fr 1fr", // CSS grid shorthand
verticalAlignment: "start",
horizontalDistribution: "stretch",
items: [
{ type: "Text", content: "" },
{ type: "Text", content: " \n " },
]
}
iframe Elements
iframe elements allow you to embed external content by providing a URL to charts, dashboards, or other web applications directly within your panels.
{
type: "Iframe",
url: "https://example.com/dashboard",
height: 400,
}
Divider Elements
Divider elements provide visual separation between sections of content in your panels.
{
type: "Divider",
}
Panel State Management
Creating and Updating Panels
A panel is identified by its ID, which must be created using createPanelId
. Custom IDs are not supported to prevent conflicts with other panels.
Use createOrUpdatePanel
for most panel scenarios. This declarative method lets you specify what the panel should contain, and it handles both creating new panels and updating existing ones with the same API call.
const panelId = await felt.createPanelId();
const greetingElement = { id: "greeting", type: "Text", content: "Hello" };
// Initial state
await felt.createOrUpdatePanel({
panel: {
id: panelId,
title: "My Panel",
body: [greetingElement]
}
});
// Update using destructuring
await felt.createOrUpdatePanel({
panel: {
id: panelId,
title: "My Panel",
body: [{ ...greetingElement, content: "Hello World" }]
}
});
Targeted Panel Element Updates
Use updatePanelElements
for granular control when you want to modify individual elements. Elements need IDs to be targeted for updates. You can also use createPanelElements
to add elements and deletePanelElements
to remove elements by their IDs.
const panelId = await felt.createPanelId();
// Create panel with multiple elements
await felt.createOrUpdatePanel({
panel: {
id: panelId,
title: "Data Panel",
body: [
{ id: "status-text", type: "Text", content: "Ready" },
{
id: "layer-select",
type: "Select",
label: "Choose Layer",
options: [
{ value: "layer1", label: "Population" },
{ value: "layer2", label: "Income" }
]
}
]
}
});
// Update only the text element
await felt.updatePanelElements({
panelId,
elements: [{
element: {
id: "status-text",
type: "Text",
content: "Processing data..."
}
}]
});
// Add a new element to the panel
await felt.createPanelElements({
panelId,
elements: [{
element: {
id: "progress-text",
type: "Text",
content: "Progress: 50%"
},
container: "body",
placement: { at: "end" }
}]
});
// Remove an element from the panel
await felt.deletePanelElements({
panelId,
elements: ["progress-text"]
});
Last updated
Was this helpful?