Settings Components

React components for building Settings views that match the Studio design system

Settings Components

TelemetryOS applications include a Settings view that appears in the Studio admin portal, allowing users to configure application behavior without touching code. To ensure a consistent user experience across all applications, the SDK provides a library of pre-styled React components that automatically match Studio's design system, including support for light and dark color schemes. These components handle visual styling, spacing, and accessibility, letting developers focus on functionality rather than presentation.

The SDK provides React components for building Settings views that match the Studio design system. Import from @telemetryos/sdk/react.

Always use these components for Settings views. Raw HTML elements won't look correct in Studio.

Debounce Guidelines

Store hooks accept an optional debounce delay (default 0ms - immediate). Choose based on input type:

Input TypeDebounceReason
Text input250msWait for typing to pause
Textarea250msWait for typing to pause
Select/Dropdown0ms (default)Immediate feedback expected
Switch/Toggle0ms (default)Immediate feedback expected
Checkbox0ms (default)Immediate feedback expected
Radio0ms (default)Immediate feedback expected
Slider5msResponsive feel, reduced message traffic
Color picker5msResponsive feel while dragging

Usage:

// Text input - debounce to wait for typing to pause
const [isLoading, city, setCity] = useCityStoreState(250)

// Dropdown - immediate (default, no argument needed)
const [isLoading, league, setLeague] = useLeagueStoreState()

// Slider - responsive (5ms)
const [isLoading, volume, setVolume] = useVolumeStoreState(5)

Container & Layout

SettingsContainer

Root wrapper for all Settings content. Handles color scheme synchronization with Studio.

import { SettingsContainer } from '@telemetryos/sdk/react'

function Settings() {
  return (
    <SettingsContainer>
      {/* All settings content goes here */}
    </SettingsContainer>
  )
}

SettingsHeading

Section heading used to divide settings into logical sections. Can be used at the container level or inside a SettingsBox.

import { SettingsHeading, SettingsDivider } from '@telemetryos/sdk/react'

<SettingsHeading>Display Options</SettingsHeading>
{/* Fields for this section */}

<SettingsDivider />

<SettingsHeading>Advanced Settings</SettingsHeading>
{/* Fields for next section */}

SettingsBox

Bordered container, typically used for individual items in a repeatable list. Each item in a list gets its own box.

import { SettingsBox, SettingsHeading, SettingsButtonFrame } from '@telemetryos/sdk/react'

<SettingsHeading>Teams</SettingsHeading>

{teams.map((team, index) => (
  <SettingsBox key={index}>
    <SettingsHeading>Team {index + 1}</SettingsHeading>
    {/* Team fields */}
    <SettingsButtonFrame>
      <button onClick={() => removeTeam(index)}>Remove</button>
    </SettingsButtonFrame>
  </SettingsBox>
))}

<SettingsButtonFrame>
  <button onClick={addTeam}>+ Add Team</button>
</SettingsButtonFrame>

SettingsDivider

Horizontal rule separator between sections.

import { SettingsDivider } from '@telemetryos/sdk/react'

<SettingsDivider />

Field Structure

SettingsField, SettingsLabel

Wrapper for each form field with its label. SettingsField renders as a <label> element, so clicking the label text will activate the input inside.

import { SettingsField, SettingsLabel } from '@telemetryos/sdk/react'

<SettingsField>
  <SettingsLabel>Field Label</SettingsLabel>
  {/* Input component goes here */}
</SettingsField>

SettingsHint

Optional hint text displayed below a field input.

import { SettingsField, SettingsLabel, SettingsInputFrame, SettingsHint } from '@telemetryos/sdk/react'

<SettingsField>
  <SettingsLabel>API Key</SettingsLabel>
  <SettingsInputFrame>
    <input type="text" value={apiKey} onChange={(e) => setApiKey(e.target.value)} />
  </SettingsInputFrame>
  <SettingsHint>Found in the dashboard under Settings → API</SettingsHint>
</SettingsField>

SettingsError

Error message displayed below a field input.

import { SettingsField, SettingsLabel, SettingsInputFrame, SettingsError } from '@telemetryos/sdk/react'

<SettingsField>
  <SettingsLabel>Email</SettingsLabel>
  <SettingsInputFrame>
    <input type="email" value={email} onChange={(e) => setEmail(e.target.value)} />
  </SettingsInputFrame>
  {error && <SettingsError>{error}</SettingsError>}
</SettingsField>

Text Inputs

SettingsInputFrame

Styled wrapper for text inputs.

import {
  SettingsContainer,
  SettingsField,
  SettingsLabel,
  SettingsInputFrame,
} from '@telemetryos/sdk/react'
import { useTeamStoreState } from '../hooks/store'

function Settings() {
  const [isLoading, team, setTeam] = useTeamStoreState(250) // 250ms debounce for text input

  return (
    <SettingsContainer>
      <SettingsField>
        <SettingsLabel>Team Name</SettingsLabel>
        <SettingsInputFrame>
          <input
            type="text"
            placeholder="Enter team name..."
            disabled={isLoading}
            value={team}
            onChange={(e) => setTeam(e.target.value)}
          />
        </SettingsInputFrame>
      </SettingsField>
    </SettingsContainer>
  )
}

SettingsTextAreaFrame

Styled wrapper for multiline text inputs.

import { SettingsTextAreaFrame } from '@telemetryos/sdk/react'
import { useDescriptionStoreState } from '../hooks/store'

const [isLoading, description, setDescription] = useDescriptionStoreState(250) // 250ms debounce for textarea

<SettingsField>
  <SettingsLabel>Description</SettingsLabel>
  <SettingsTextAreaFrame>
    <textarea
      placeholder="Enter description..."
      disabled={isLoading}
      value={description}
      onChange={(e) => setDescription(e.target.value)}
      rows={4}
    />
  </SettingsTextAreaFrame>
</SettingsField>

Selection Inputs

SettingsSelectFrame

Styled wrapper for dropdown selects.

import { SettingsSelectFrame } from '@telemetryos/sdk/react'
import { useLeagueStoreState } from '../hooks/store'

const [isLoading, league, setLeague] = useLeagueStoreState(0) // 0ms for immediate feedback

<SettingsField>
  <SettingsLabel>League</SettingsLabel>
  <SettingsSelectFrame>
    <select
      disabled={isLoading}
      value={league}
      onChange={(e) => setLeague(e.target.value)}
    >
      <option value="nfl">NFL</option>
      <option value="nba">NBA</option>
      <option value="mlb">MLB</option>
      <option value="nhl">NHL</option>
    </select>
  </SettingsSelectFrame>
</SettingsField>

SettingsSliderFrame

Styled wrapper for range sliders.

import { SettingsSliderFrame } from '@telemetryos/sdk/react'
import { useVolumeStoreState } from '../hooks/store'

const [isLoading, volume, setVolume] = useVolumeStoreState(5) // 5ms for responsive feel

<SettingsField>
  <SettingsLabel>Volume</SettingsLabel>
  <SettingsSliderFrame>
    <input
      type="range"
      min="0"
      max="100"
      disabled={isLoading}
      value={volume}
      onChange={(e) => setVolume(Number(e.target.value))}
    />
    <span>{volume}%</span>  {/* Optional value label */}
  </SettingsSliderFrame>
</SettingsField>

The frame uses flexbox layout, so you can optionally add a <span> after the input to display the current value.

SettingsSliderRuler

Optional ruler with tick labels displayed below a slider.

import { SettingsSliderFrame, SettingsSliderRuler } from '@telemetryos/sdk/react'

<SettingsField>
  <SettingsLabel>Quality</SettingsLabel>
  <SettingsSliderFrame>
    <input
      type="range"
      min="1"
      max="3"
      value={quality}
      onChange={(e) => setQuality(Number(e.target.value))}
    />
    <span>{quality}</span>
  </SettingsSliderFrame>
  <SettingsSliderRuler>
    <span>Low</span>
    <span>Medium</span>
    <span>High</span>
  </SettingsSliderRuler>
</SettingsField>

SettingsColorFrame

Styled wrapper for color picker inputs. Displays the color swatch alongside the hex value.

import { SettingsColorFrame } from '@telemetryos/sdk/react'
import { useColorStoreState } from '../hooks/store'

const [isLoading, color, setColor] = useColorStoreState(5) // 5ms for responsive feel while dragging

<SettingsField>
  <SettingsLabel>Brand Color</SettingsLabel>
  <SettingsColorFrame>
    <input
      type="color"
      disabled={isLoading}
      value={color}
      onChange={(e) => setColor(e.target.value)}
    />
    <span>{color}</span>
  </SettingsColorFrame>
</SettingsField>

Toggle Inputs

SettingsSwitchFrame, SettingsSwitchLabel

Styled wrapper for toggle switches.

import { SettingsSwitchFrame, SettingsSwitchLabel } from '@telemetryos/sdk/react'
import { useShowScoresStoreState } from '../hooks/store'

const [isLoading, showScores, setShowScores] = useShowScoresStoreState(0) // 0ms for immediate feedback

<SettingsField>
  <SettingsSwitchFrame>
    <input
      type="checkbox"
      role="switch"
      disabled={isLoading}
      checked={showScores}
      onChange={(e) => setShowScores(e.target.checked)}
    />
    <SettingsSwitchLabel>Show Live Scores</SettingsSwitchLabel>
  </SettingsSwitchFrame>
</SettingsField>

SettingsCheckboxFrame, SettingsCheckboxLabel

Styled wrapper for checkboxes.

import { SettingsCheckboxFrame, SettingsCheckboxLabel } from '@telemetryos/sdk/react'
import { useAutoRefreshStoreState } from '../hooks/store'

const [isLoading, autoRefresh, setAutoRefresh] = useAutoRefreshStoreState(0) // 0ms for immediate feedback

<SettingsField>
  <SettingsCheckboxFrame>
    <input
      type="checkbox"
      disabled={isLoading}
      checked={autoRefresh}
      onChange={(e) => setAutoRefresh(e.target.checked)}
    />
    <SettingsCheckboxLabel>Enable Auto-Refresh</SettingsCheckboxLabel>
  </SettingsCheckboxFrame>
</SettingsField>

SettingsRadioFrame, SettingsRadioLabel

Styled wrapper for radio button groups.

import { SettingsRadioFrame, SettingsRadioLabel } from '@telemetryos/sdk/react'
import { useDisplayModeStoreState } from '../hooks/store'

const [isLoading, displayMode, setDisplayMode] = useDisplayModeStoreState(0) // 0ms for immediate feedback

<SettingsField>
  <SettingsLabel>Display Mode</SettingsLabel>
  <SettingsRadioFrame>
    <input
      type="radio"
      name="displayMode"
      value="compact"
      disabled={isLoading}
      checked={displayMode === 'compact'}
      onChange={(e) => setDisplayMode(e.target.value)}
    />
    <SettingsRadioLabel>Compact</SettingsRadioLabel>
  </SettingsRadioFrame>
  <SettingsRadioFrame>
    <input
      type="radio"
      name="displayMode"
      value="expanded"
      disabled={isLoading}
      checked={displayMode === 'expanded'}
      onChange={(e) => setDisplayMode(e.target.value)}
    />
    <SettingsRadioLabel>Expanded</SettingsRadioLabel>
  </SettingsRadioFrame>
</SettingsField>

Actions

SettingsButtonFrame

Styled wrapper for action buttons.

import { SettingsButtonFrame } from '@telemetryos/sdk/react'

<SettingsButtonFrame>
  <button onClick={handleReset}>Reset to Defaults</button>
</SettingsButtonFrame>

Complete Settings Example

A complete Settings view demonstrating the recommended patterns:

// Store hooks - see store-hooks documentation for how to define these in hooks/store.ts
import {
  useTeamsStoreState,
  useRefreshIntervalStoreState,
  useShowScoresStoreState,
  useBackgroundColorStoreState,
} from '../hooks/store'
import {
  SettingsContainer,
  SettingsBox,
  SettingsHeading,
  SettingsDivider,
  SettingsField,
  SettingsLabel,
  SettingsHint,
  SettingsInputFrame,
  SettingsSelectFrame,
  SettingsSliderFrame,
  SettingsColorFrame,
  SettingsSwitchFrame,
  SettingsSwitchLabel,
  SettingsButtonFrame,
} from '@telemetryos/sdk/react'

export default function Settings() {
  // Store hooks return [isLoading, value, setValue]
  // Debounce: 250ms for text inputs, 0ms (default) for toggles, 5ms for sliders/colors
  const [isLoadingTeams, teams, setTeams] = useTeamsStoreState(250) // contains text inputs
  const [isLoadingInterval, interval, setInterval] = useRefreshIntervalStoreState(5)
  const [isLoadingScores, showScores, setShowScores] = useShowScoresStoreState()
  const [isLoadingBg, backgroundColor, setBackgroundColor] = useBackgroundColorStoreState(5)

  const isLoading = isLoadingTeams || isLoadingInterval || isLoadingScores || isLoadingBg

  const addTeam = () => {
    setTeams([...teams, { name: '', league: 'nfl' }])
  }

  const removeTeam = (index: number) => {
    setTeams(teams.filter((_, i) => i !== index))
  }

  const updateTeam = (index: number, updates: Partial<typeof teams[0]>) => {
    const updated = [...teams]
    updated[index] = { ...updated[index], ...updates }
    setTeams(updated)
  }

  return (
    <SettingsContainer>

      <SettingsHeading>Teams</SettingsHeading>

      {teams.map((team, index) => (
        <SettingsBox key={index}>
          <SettingsHeading>Team {index + 1}</SettingsHeading>

          <SettingsField>
            <SettingsLabel>Team Name</SettingsLabel>
            <SettingsInputFrame>
              <input
                type="text"
                placeholder="Enter team name..."
                disabled={isLoading}
                value={team.name}
                onChange={(e) => updateTeam(index, { name: e.target.value })}
              />
            </SettingsInputFrame>
            <SettingsHint>This name appears in the header</SettingsHint>
          </SettingsField>

          <SettingsField>
            <SettingsLabel>League</SettingsLabel>
            <SettingsSelectFrame>
              <select
                disabled={isLoading}
                value={team.league}
                onChange={(e) => updateTeam(index, { league: e.target.value })}
              >
                <option value="nfl">NFL</option>
                <option value="nba">NBA</option>
                <option value="mlb">MLB</option>
                <option value="nhl">NHL</option>
              </select>
            </SettingsSelectFrame>
          </SettingsField>

          <SettingsButtonFrame>
            <button type="button" disabled={isLoading} onClick={() => removeTeam(index)}>
              Remove Team
            </button>
          </SettingsButtonFrame>
        </SettingsBox>
      ))}

      <SettingsButtonFrame>
        <button type="button" disabled={isLoading} onClick={addTeam}>+ Add Team</button>
      </SettingsButtonFrame>

      <SettingsDivider />

      <SettingsHeading>Display</SettingsHeading>

      <SettingsField>
        <SettingsLabel>Refresh Interval (seconds)</SettingsLabel>
        <SettingsSliderFrame>
          <input
            type="range"
            min="10"
            max="120"
            disabled={isLoading}
            value={interval}
            onChange={(e) => setInterval(Number(e.target.value))}
          />
          <span>{interval}s</span>
        </SettingsSliderFrame>
      </SettingsField>

      <SettingsField>
        <SettingsSwitchFrame>
          <input
            type="checkbox"
            role="switch"
            disabled={isLoading}
            checked={showScores}
            onChange={(e) => setShowScores(e.target.checked)}
          />
          <SettingsSwitchLabel>Show Live Scores</SettingsSwitchLabel>
        </SettingsSwitchFrame>
      </SettingsField>

      <SettingsField>
        <SettingsLabel>Background Color</SettingsLabel>
        <SettingsColorFrame>
          <input
            type="color"
            disabled={isLoading}
            value={backgroundColor}
            onChange={(e) => setBackgroundColor(e.target.value)}
          />
          <span>{backgroundColor}</span>
          <SettingsButtonFrame>
            <button type="button" disabled={isLoading} onClick={() => setBackgroundColor('transparent')}>
              Clear
            </button>
          </SettingsButtonFrame>
        </SettingsColorFrame>
      </SettingsField>
    </SettingsContainer>
  )
}

Component Reference

ComponentPurpose
SettingsContainerRoot wrapper, handles color scheme
SettingsHeadingSection heading
SettingsBoxContainer for list items
SettingsDividerHorizontal separator
SettingsFieldWrapper for each field (renders as label)
SettingsLabelField label
SettingsHintHelp text below a field
SettingsErrorError message below a field
SettingsInputFrameText input wrapper
SettingsTextAreaFrameMultiline text wrapper
SettingsSelectFrameDropdown wrapper
SettingsSliderFrameRange slider wrapper
SettingsSliderRulerTick labels below a slider
SettingsColorFrameColor picker wrapper
SettingsSwitchFrameToggle switch wrapper
SettingsSwitchLabelToggle switch label
SettingsCheckboxFrameCheckbox wrapper
SettingsCheckboxLabelCheckbox label
SettingsRadioFrameRadio button wrapper
SettingsRadioLabelRadio button label
SettingsButtonFrameAction button wrapper