Skip to main content

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:

  1. Click Duplicate icon on the global control
  2. Set UI to the target UI ID
  3. Set Enabled = No
  4. 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 component adds a control on top of the inherited set.
  • A row with the same component as an inherited control and enabled = false suppresses 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 component still suppresses it.

The Select tab shows a banner reminding you of this when a space-linked UI is selected.

Auto-surfaced Weather control

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

  1. Select a UI from the dropdown at the top
  2. Choose controls from the left list (multi-select)
  3. Click Add ▶ to add them to the UI
  4. 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:

  1. Select UI: kitchen_panel
  2. Select controls: Lighting, Shades, Climate
  3. Click Add ▶
  4. Controls appear in kitchen panel navigation

Creating Custom Control

  1. Switch to List tab
  2. Click Add
  3. Configure:
    Label: Security
    Component: /controls/security
    UI: master_bedroom_panel
    Icon: shield
    PIN: Yes (require PIN)
    Enabled: Yes
  4. Save

Hiding Global Control from Specific UI

To hide "Video" from guest panel:

  1. Find "Video" global control in list
  2. Click Duplicate icon
  3. In the copy, set:
    UI: guest_panel
    Enabled: No
  4. Save
  5. Video control no longer appears in guest_panel

Reordering Controls

Using List tab:

  1. Enable Sort action
  2. Drag controls to desired order
  3. Sort index updates automatically
  4. Changes take effect immediately in UIs

Or using Select tab (right side):

  1. Select a control from the right list
  2. Use ▲ ▼ buttons to move up/down
  3. 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_order attribute (Attributes tab) restores the sort-index ordering.
  • The Lock Control Order switch on the UI editor's Display tab (lock_control_order setting) 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
  • Doors2 unlocked, or Locked once every door with a known state is secured
  • SecurityArmed · 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 active whenever 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 like heating or running read 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:

  1. Edit the control (e.g., "Security")
  2. Set PIN: Yes
  3. Save
  4. 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_id attributes (stamped by the lutron_qsx driver's sync_areas command — 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 Sheer vs. 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 √n columns) 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 garage subsystem — 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_action attributes — discrete openers carry these (stamped by their driver), pulse-relay doors do not and fall back to pulse. 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 operation attribute), 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_zone attribute on the garage subsystem
  • 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 door subsystem — 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 state and 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 level and no state attribute (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 Fan and Patio Fan instead of two tiles both reading Fan). 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's temperature and system_mode attributes. 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 irrigation subsystem — 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 irrigation subsystem (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:

  1. Develop Svelte component in src/components/controls/
  2. Add route in src/routes/controls/
  3. Create UI control pointing to the component
  4. 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 bulb
  • audio - Speaker
  • video - TV/display
  • shades - Window blinds
  • climate - Thermometer
  • security - Shield
  • camera - Camera
  • pool - Pool
  • irrigation - Sprinkler
  • lock - Padlock

Custom Icons

Upload custom icons via UI > UI Themes page:

  1. Navigate to UI > UI Themes
  2. Select theme
  3. Go to Icons tab
  4. Upload PNG or SVG files
  5. 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

  1. Consistent Labeling: Use clear, recognizable names for controls

  2. Logical Ordering: Arrange controls by frequency of use:

    • Most used first (Lighting, Audio)
    • Moderate use middle (Climate, Shades)
    • Least used last (Pool, Irrigation)
  3. Icon Selection: Choose icons that clearly represent the control function

  4. Global Defaults: Use global controls for controls that should appear everywhere

  5. UI Overrides: Use UI-specific controls sparingly, only when truly needed

  6. PIN Protection: Only PIN-protect controls that need security:

    • Security system
    • Administrative functions
    • Potentially dangerous controls (gates with traffic)
  7. Testing: Test controls after adding to verify component exists and loads

  8. Enabled State: Disable rather than delete during troubleshooting

Troubleshooting

Control Not Appearing

Check:

  1. Enabled: Control must be enabled
  2. UI Assignment: Control is global OR assigned to this UI
  3. Component Exists: Svelte component file exists at path
  4. UI Enabled: Parent UI must be enabled
  5. Role Permission: User's role allows access to this UI

Control Shows Error

Check:

  1. Component Path: Must start with /
  2. Component Exists: File exists at src/routes/controls/{name}.svelte
  3. Syntax Errors: Component has valid Svelte syntax
  4. Console Errors: Check browser console for runtime errors

Wrong Icon Displays

Check:

  1. Icon Name: Matches a file in the theme's /icons/ directory or GEM's built-in icon set
  2. Theme: Correct theme assigned to UI — if the theme doesn't ship the icon, the built-in glyph displays instead
  3. File Extension: Don't include .png or .svg in icon name
  4. Browser Cache: Clear cache and reload

PIN Not Working

Check:

  1. User PIN: User has PIN set in user account
  2. PIN Match: Entered PIN matches user PIN
  3. 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:

  1. Custom control component with conditional display
  2. JavaScript in theme
  3. 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:

  1. Create template UI with all desired controls
  2. Duplicate UI
  3. Modify specific controls as needed
  4. Saves time vs. configuring from scratch
  • UIs - Managing user interfaces
  • UI Zones - Assigning zones to controls
  • UI Themes - Icon management
  • Roles - Controlling access to controls