UI Controls
UI Controls are navigation buttons and pages that appear in the main navigation of each UI. They determine which control pages (Lighting, AV, HVAC, etc.) are accessible from each user interface.
Overview
UI Controls allow you to:
- Customize which control pages appear in each UI
- Create UI-specific controls (hide irrelevant controls)
- Configure control icons and labels
- Reorder navigation buttons
- Set PIN protection for specific controls
Viewing UI Controls
The page has two tabs: List and Select.
List Tab
The main grid displays all UI controls with the following columns:
- ID - Unique identifier
- Label - Display text on navigation button
- Component - Svelte component path
- UI - UI this control belongs to (null = global)
- Icon - Icon filename
- PIN - Whether PIN is required to access
- Enabled - Whether the control is active
- Sort Index - Display order
Grid Actions
- Add - Create a new UI control
- Delete - Remove a UI control
- Sort - Reorder controls by dragging rows
Quick Actions
- Duplicate Icon - Copy an existing control
Understanding Global vs. UI-Specific Controls
Global Controls (UI = null)
- Appear in ALL UIs by default
- Cannot be removed from a specific UI
- Include standard controls: Lighting, Audio, Video, Climate
UI-Specific Controls (UI = specific ID)
- Appear only in the assigned UI
- Override global controls when present
- Allow per-UI customization
Hiding Global Controls
To hide a global control from a specific UI:
- Click Duplicate icon on the global control
- Set UI to the target UI ID
- Set Enabled = No
- Save
Result: The disabled UI-specific control overrides the global control, effectively hiding it from that UI.
Tip: A tooltip on the List tab explains this: "To hide a global control, copy it, add it to a specific UI, then disable it"
Space-Linked UIs
When a UI has site_space_id set, its control list is auto-derived from the subsystems present in the linked Site Space (and descendants if include_child_spaces is on). Explicit ui_control rows are still honored as overlays:
- A row with a new
componentadds a control on top of the inherited set. - A row with the same
componentas an inherited control andenabled = falsesuppresses that inherited control. - Global ("All UIs") controls overlay space-linked UIs too. A control assigned to All UIs now appears on space-linked UIs alongside the auto-derived set, matching how non-space UIs union global controls. A per-UI disabled row for the same
componentstill suppresses it.
The Select tab shows a banner reminding you of this when a space-linked UI is selected.
Because a space-linked UI's controls are derived from the zoned subsystems in its space, non-zoned pages like Weather would never appear automatically. When one or more weather locations are configured in the system settings, a /controls/weather control is auto-injected into space-linked UIs (sorted toward the end of the menu). Add a disabled /controls/weather overlay row to suppress it, or it falls away on its own once all weather locations are removed.
Select Tab
The Select tab provides a quick way to add standard controls to a UI:
Workflow
Left Side - Available Controls:
- Lists all subsystem-based controls
- Includes: Lighting, Shades, Security, HVAC, etc.
- Plus special controls: Audio, Video
Center - Action Buttons:
- Add ▶ - Add selected controls to UI
- ◀ Remove - Remove selected controls from UI
Right Side - Current UI Controls:
- Shows controls already assigned to selected UI
- Sorted by sort_index
Using the Select Tab
- Select a UI from the dropdown at the top
- Choose controls from the left list (multi-select)
- Click Add ▶ to add them to the UI
- Or select existing controls on the right and click ◀ Remove
Component Path: Controls are automatically mapped to /controls/{subsystem_lowercase}
Example:
Lighting → /controls/lighting
Audio → /controls/audio
Shades → /controls/shades
Control Configuration
Basic Properties
Label
- Text shown on navigation button
- Examples: "Lighting", "Audio", "Climate", "Cameras"
- Can be customized per UI
Component
- Svelte component path
- Must start with
/ - Standard format:
/controls/{name} - Custom components:
/controls/custom_control
UI
- Leave blank for global control (all UIs)
- Select specific UI for UI-only control
- Use for UI customization
Icon
- Icon filename (without extension)
- Resolved theme-first: the theme's
/icons/directory is checked, then GEM's built-in icon set - Examples:
lighting,audio,shades,security - If not set, defaults based on component name
PIN
- Require PIN entry before accessing control
- Useful for:
- Parental controls
- Secure controls (security system)
- Administrative functions
Enabled
- Toggle to show/hide control
- Disabled controls don't appear in navigation
- Configuration preserved
Sort Index
- Display order in navigation
- Lower numbers appear first
- Drag rows in the grid, then click the Save Sort Order button (swap-sort icon) in the toolbar to renumber
Common Use Cases
Adding Standard Controls to a UI
Using Select tab:
- Select UI:
kitchen_panel - Select controls: Lighting, Shades, Climate
- Click Add ▶
- Controls appear in kitchen panel navigation
Creating Custom Control
- Switch to List tab
- Click Add
- Configure:
Label: SecurityComponent: /controls/securityUI: master_bedroom_panelIcon: shieldPIN: Yes (require PIN)Enabled: Yes
- Save
Hiding Global Control from Specific UI
To hide "Video" from guest panel:
- Find "Video" global control in list
- Click Duplicate icon
- In the copy, set:
UI: guest_panelEnabled: No
- Save
- Video control no longer appears in guest_panel
Reordering Controls
Using List tab:
- Enable Sort action
- Drag controls to desired order
- Sort index updates automatically
- Changes take effect immediately in UIs
Or using Select tab (right side):
- Select a control from the right list
- Use ▲ ▼ buttons to move up/down
- Sort index updates automatically
User Reordering (press and hold)
End users can reorder the controls grid directly on their UI: press and
hold a tile until it lifts, drag it to a new position, and release. The
order is saved per UI as the control_order attribute and overlays the
integrator-defined sort — it also drives the nav rail / bottom tab bar
shortcuts (nav_layout: rail), so the first few controls in the grid are
the ones that appear as shortcuts.
- Controls the integrator adds later append after the user-ordered ones, in their sort index position.
- Deleting the UI's
control_orderattribute (Attributes tab) restores the sort-index ordering. - The Lock Control Order switch on the UI editor's Display tab
(
lock_control_ordersetting) disables the gesture for that UI — set it on the shared defaults (ui 0) to lock every UI, and override per UI where reordering should stay available. The lock is enforced server-side.
Tile Status Headlines
Each tile on the controls grid shows a live status line beneath its label, and the tile takes the accent color while something is on/playing/open:
- Audio / Video — playing zone count (
2 zones playing) or Off - Lighting / Fans / Power — zones on (
3 on) or Off - Shades — zones open (
2 open) or Closed - Garage / Gates — doors open (
1 open) or Closed - Doors —
2 unlocked, or Locked once every door with a known state is secured - Security — Armed · Away (with the mode) or Disarmed
- Climate — current temperature (
72°) - Cameras — camera count
- Custom subsystems — any subsystem you created yourself (a sauna, pool equipment, a brewery) gets a generic headline automatically:
N activewhenever zones are in an active state. A zone counts as active when it reports a level above zero or any state that isn't a common at-rest word (off,closed,locked,disarmed,idle,standby,ok,normal,clear,secure,dry,offline,disabled, …) — unrecognized vocabularies likeheatingorrunningread as active, so custom subsystems light up with no configuration. The weather tile shows no headline
Headlines update in real time from the server's per-space status rollup where available, falling back to a zone snapshot taken when the page opens.
PIN-Protected Controls
For secure access to certain controls:
- Edit the control (e.g., "Security")
- Set PIN: Yes
- Save
- When users click the control:
- PIN prompt appears
- Must enter valid PIN (from user account)
- On success, control opens
- On failure, access denied
Control Components
Standard Control Components
GEM includes built-in control components:
/controls/lighting
- Zone grid for lighting control
- Supports on/off, dimming, color
- Scene buttons if configured
- The left pane switches between Keypad, Scenes, and Groups via a segmented control in the bottom bar; individual lights stay in the right pane
- The Keypad segment appears automatically when the UI carries
lutron_control_station_idattributes (stamped by thelutron_qsxdriver'ssync_areascommand — zero configuration) and renders the area's physical Lutron keypads on-screen with live LED state, one per station, on a shared wallplate like the installed hardware. It becomes the default left-pane view when present, so clients land on the wall controls they already know — station discovery reads the UI's settings directly, so the keypad view paints immediately without flashing the scene list first. If the Lutron processor device is removed, the segment hides rather than showing errors - In the All group view, a light whose label doesn't already start with its zone group's name leads with the group label, so same-named lights in different rooms read apart; individual group views keep the original labels
- A View Active button in the bottom bar trims the list to lights that are currently on, judged from each zone's tracked state/level — a light hides when its state is
off, its level is 0, or it reports no state at all; every control type is judged the same way. The same button toggles back to View All and highlights while the filter is engaged. The filter is suspended while editing a scene, which needs the full list. When the filter is on and no lights qualify, the list shows No active zones instead of an empty column
/controls/audio
- Audio zone control
- Volume, input selection, transport
- Multi-room audio grouping
/controls/video
- Video zone control
- Source selection, display control
- AV matrix routing
/controls/shades
- Shade position control
- Open, close, stop, presets
- Group control
- In the All group view, a shade whose label doesn't already start with its zone group's name leads with the group label (e.g.
Master Sheervs.Study Sheer); individual group views keep the original labels - A View Active button in the bottom bar trims the list to shades that are open, judged from each zone's tracked state/level — a shade hides when its state is
closed/off, its level is 0, or it reports no state at all; mid-travel states (opening, closing, stopped) count as open, and every control type is judged the same way. The same button toggles back to View All and highlights while the filter is engaged. When the filter is on and no shades qualify, the list shows No active zones instead of an empty column
/controls/climate
- Thermostat control
- Temperature, mode, fan
- Multi-zone HVAC
/controls/security
- Alarm panel interface
- Arm, disarm, status
- Zone bypass
/controls/cameras
- Camera feeds
- PTZ control
- Recording playback
- When viewing a group, tiles auto-arrange to fit the screen — the grid is sized to the number of cameras (1, 2×2, 3×3, etc.) and reflows for portrait vs. landscape orientation. Groups of 13 or more fall back to a generalized square-ish grid (roughly
√ncolumns) so every tile stays visible; portrait keeps two columns and stacks the rest - Tapping a tile expands that camera to full screen; tapping again returns to the group
/controls/garage
- Garage door control for the
garagesubsystem — All / None selection, then raise or close the selected doors - Handles two opener styles automatically per zone:
- Pulse-relay doors (a single momentary contact that toggles the door) — raise/close only fire when the door isn't already where you want it, and press-and-hold the raise/close button forces a pulse regardless of the sensed state (escape hatch for a wrong door sensor)
- Discrete openers (separate open/close/stop, e.g. Konnected) — raise/close send the verb directly and idempotently (no toggle guard); a Stop button appears whenever any door in the fleet supports a real mid-travel stop
- Opener style is detected from the zone's
open_action/close_action/stop_actionattributes — discrete openers carry these (stamped by their driver), pulse-relay doors do not and fall back topulse. See Command Overrides - Renders an interactive zone map when garage zones are placed on a map (see UI Themes → Zone Maps); otherwise falls back to an inline camera feed alongside a row of labeled zone-select buttons, one per door
- Each zone-select button shows the door's live status as a colored badge below its label — opening / closing in blue while the door is travelling (from the zone's
operationattribute), open in amber (worth noticing), and closed in green (secured). Relay-only doors that don't report an operation show just open/closed - The inline camera feed is sourced from the
camera_zoneattribute on thegaragesubsystem - The camera-feed fallback is orientation-aware — the camera occupies half the width in landscape and the full width in portrait
/controls/doors
- Door and lock control for the
doorsubsystem — a grid of zone-select tiles (one per door or lock) alongside the selected zone's full control panel - Each tile shows the door's live state and battery level as a colored badge below its label, updated in real time via zone subscriptions:
- closed / locked in green (secured)
- open / unlocked / ajar in amber (worth noticing)
- opening / closing / moving in blue (in motion)
- fault / alarm / error / offline in red
- any other state falls back to the generic status colors
- When a zone reports a battery level it is appended to the badge as a percentage (e.g.
closed - 87%) - This live badge now renders for every subsystem that uses the generic zone-grid control (doors, gates, covers, power, water, irrigation, fire, lift, fans, …), driven by each zone's
stateand battery (battery_level/battery) attributes via live subscriptions. A zone that reports neither simply shows a plain label, so subsystems whose zones don't publish state are unaffected - Fan zones that report only a
leveland nostateattribute (switched fan legs on drivers that don't push a named state) derive the badge from the level — above 0 shows on, otherwise off — so the zone tiles agree with the fans page headline count. Fans only: for shades and lights, level means position/brightness, not on/off - In the All group view, a tile whose zone label doesn't already start with its zone group's name leads with the group label (e.g.
Garage FanandPatio Faninstead of two tiles both readingFan). Individual group views keep the original labels. The Lighting and Shades pages do the same in their All view - Climate / HVAC zones in the generic zone grid show a different badge: the zone's current temperature and system mode (e.g.
72° Cool) instead of the open/closed + battery badge, driven by each zone'stemperatureandsystem_modeattributes. The badge is color-coded by mode — cool (blue), heat (red), auto (green), dry (orange), fan (cyan), and off/idle (gray) - A View Active button in the bottom bar (shown when the grid has more than one zone) trims the tiles to zones in an attention-worthy state, judged from the same state that drives the badge — off/closed/locked/disarmed/idle/offline-style at-rest states, and zones reporting no state at all, are hidden, while climate zones count as active unless their mode is off. "Active" means the same thing here as on the controls page — both read one central at-rest state registry (see Tile Status Headlines), so what the filter keeps matches the tile headline count. The same button toggles back to View All and highlights while the filter is engaged, and the filter resets each time the page opens. When the filter is on and no zones qualify, the grid shows No active zones instead of an empty pane
/controls/water
- Water management control
- Leak sensors, shut-off valves
- Flow monitoring
/controls/irrigation
- Irrigation control for the
irrigationsubsystem — a zone grid of sprinkler valves, stations, and controllers - Uses the generic zone-grid control, so each zone tile carries the live state badge (and battery level where a zone reports one), updated in real time via zone subscriptions
- Zones placed by a sprinkler-controller driver land here automatically when filed under the standard
irrigationsubsystem (see Subsystems)
/controls/weather
- Current conditions, hourly strip, and 7-day forecast for each configured weather location (data from the free Open-Meteo API — no key required)
- Per-location live radar view (radar.weather.gov map, or a legacy NWS station loop image)
- Locations, ordering, default city, and radar views are configured on the UI editor's Weather tab (see UIs)
/controls/pool
- Pool equipment control
- Temperature, pumps, lights
- Spa control
Custom Control Components
Create custom control components:
- Develop Svelte component in
src/components/controls/ - Add route in
src/routes/controls/ - Create UI control pointing to the component
- Assign to UI
Example Custom Control:
<!-- src/routes/controls/outdoor_audio.svelte -->
<script>
import GemApp from '../../gem/app';
// Custom outdoor audio control logic
</script>
<div class="outdoor-audio-control">
<!-- Custom UI for outdoor audio zones -->
</div>
Then create UI control:
Label: Outdoor Audio
Component: /controls/outdoor_audio
Icon: speaker_outdoor
Icons
Icons resolve theme-first: if the active theme ships the file in its /icons/ directory, that glyph is used on the control tiles and nav; otherwise GEM's built-in icon set is used. A theme only needs to include the icons it wants to restyle.
Standard Icons
lighting- Light bulbaudio- Speakervideo- TV/displayshades- Window blindsclimate- Thermometersecurity- Shieldcamera- Camerapool- Poolirrigation- Sprinklerlock- Padlock
Custom Icons
Upload custom icons via UI > UI Themes page:
- Navigate to UI > UI Themes
- Select theme
- Go to Icons tab
- Upload PNG or SVG files
- Reference by filename in UI Control
Icon Requirements:
- Format: PNG or SVG
- Size: 64x64 to 128x128 pixels
- Style: Should match theme aesthetic
- Filename: lowercase_with_underscores.png
Best Practices
-
Consistent Labeling: Use clear, recognizable names for controls
-
Logical Ordering: Arrange controls by frequency of use:
- Most used first (Lighting, Audio)
- Moderate use middle (Climate, Shades)
- Least used last (Pool, Irrigation)
-
Icon Selection: Choose icons that clearly represent the control function
-
Global Defaults: Use global controls for controls that should appear everywhere
-
UI Overrides: Use UI-specific controls sparingly, only when truly needed
-
PIN Protection: Only PIN-protect controls that need security:
- Security system
- Administrative functions
- Potentially dangerous controls (gates with traffic)
-
Testing: Test controls after adding to verify component exists and loads
-
Enabled State: Disable rather than delete during troubleshooting
Troubleshooting
Control Not Appearing
Check:
- Enabled: Control must be enabled
- UI Assignment: Control is global OR assigned to this UI
- Component Exists: Svelte component file exists at path
- UI Enabled: Parent UI must be enabled
- Role Permission: User's role allows access to this UI
Control Shows Error
Check:
- Component Path: Must start with
/ - Component Exists: File exists at
src/routes/controls/{name}.svelte - Syntax Errors: Component has valid Svelte syntax
- Console Errors: Check browser console for runtime errors
Wrong Icon Displays
Check:
- Icon Name: Matches a file in the theme's
/icons/directory or GEM's built-in icon set - Theme: Correct theme assigned to UI — if the theme doesn't ship the icon, the built-in glyph displays instead
- File Extension: Don't include .png or .svg in icon name
- Browser Cache: Clear cache and reload
PIN Not Working
Check:
- User PIN: User has PIN set in user account
- PIN Match: Entered PIN matches user PIN
- Case Sensitivity: PINs are typically numeric (no case)
Duplicate Not Working
Cause: Duplicate creates a copy but doesn't auto-save
Solution: After clicking duplicate, configure the new control and save explicitly
Advanced Topics
Conditional Controls
Show/hide controls based on system state:
Not directly supported in UI Controls page. Implement via:
- Custom control component with conditional display
- JavaScript in theme
- Macro that enables/disables controls
Dynamic Control Counts
Display count badges on controls:
Example: Show number of lights that are on
Implement in control component:
let onCount = zones.filter(z => z.power === 'on').length;
Control Templates
For installations with many similar UIs:
- Create template UI with all desired controls
- Duplicate UI
- Modify specific controls as needed
- Saves time vs. configuring from scratch