Skip to content

PreferencesPanel, PreferencesPanelSection, PreferencesControl, PreferencesTabs, and PreferencesTab

PreferencesModel and PreferencesDialog documents the dialog as a whole; this page covers the five smaller pieces (all from scenerystack/joist) it's assembled from. Two of them — PreferencesPanelSection and PreferencesControl — are the ones sim authors actually construct, via a PreferencesModel option's customPreferences: [{ createContent: ( parentTandem ) => Node }]. The other three — PreferencesPanel, PreferencesTabs, and PreferencesTab — are the scaffolding PreferencesDialog uses internally to build each tab and its panel; documented here for completeness, but you won't normally construct them yourself.

ts
import { PreferencesModel, Sim, onReadyToLaunch } from 'scenerystack/sim';
import { PreferencesPanelSection, PreferencesControl } from 'scenerystack/joist';
import { Text } from 'scenerystack/scenery';
import { ToggleSwitch } from 'scenerystack/sun';
import { BooleanProperty, Property } from 'scenerystack/axon';

const showGridProperty = new BooleanProperty( false );

const preferencesModel = new PreferencesModel( {
  simulationOptions: {
    customPreferences: [ {
      createContent: parentTandem => {
        const label = new Text( 'Show grid' );
        const toggle = new ToggleSwitch( showGridProperty, false, true, {
          tandem: parentTandem.createTandem( 'showGridSwitch' )
        } );

        // PreferencesControl handles the label/control/description layout for you.
        const control = new PreferencesControl( {
          labelNode: label,
          controlNode: toggle
        } );

        // PreferencesPanelSection adds the standard section spacing/indentation.
        return new PreferencesPanelSection( { contentNode: control } );
      }
    } ]
  }
} );

onReadyToLaunch( () => {
  const sim = new Sim( new Property( 'My Simulation' ), [ /* screens */ ], { preferencesModel } );
  sim.start();
} );

PreferencesControl

A GridBox that lays out an optional labelNode, an optional controlNode, and an optional descriptionNode in the standard Preferences arrangement — label and control on one row, a full-width description below — and wires up sensible accessibility defaults between them.

OptionDefaultEffect
labelNodeNode placed at the left of the control's row
controlNodeThe actual UI control (a switch, radio buttons, combo box, …); right-aligned
descriptionNodeNode placed below the label/control row, spanning both columns
labelSpacing10Horizontal gap between labelNode and controlNode when there's no descriptionNode
ySpacing5Vertical gap between the label/control row and descriptionNode
allowDescriptionStretchtrueIf true, the description cell stretches to a minimum content width (480) so the control aligns with other controls in the panel
headingControlfalseSet true for a control acting as a section heading — it won't stretch to fill the row like an ordinary control does

If controlNode has no accessibleName/accessibleHelpText already set, PreferencesControl derives them from labelNode/descriptionNode's text automatically. PreferencesControl also manages disabledOpacity on itself, so nesting it inside another disableable container doesn't compound dimming.

PreferencesPanelSection

A VBox providing the standard vertical spacing and title/content indentation for one logical group of preferences within a tab.

OptionDefaultEffect
titleNodenullIf provided, a heading for this section
contentNodenullIf provided, the section's body — indented under titleNode by contentLeftMargin
contentLeftMargin30Indentation applied to contentNode when a titleNode is also present
contentNodeOptions{}Extra NodeOptions applied to the wrapper around contentNode

For a customPreferences entry with just one control and no separate heading (as in the example above), passing only contentNode (no titleNode) is the common case.

PreferencesPanel

The Node base class each of joist's built-in tab panels (Simulation, Visual, Audio, Input, Localization) extends. Its entire job is visibility: it shows itself only when selectedTabProperty matches its own PreferencesType and tabVisibleProperty is true.

ts
new PreferencesPanel(
  preferencesType: PreferencesType,
  selectedTabProperty: TReadOnlyProperty<PreferencesType>,
  tabVisibleProperty: TReadOnlyProperty<boolean>,
  providedOptions?: PreferencesPanelOptions
)

You would only construct one directly if building an entirely custom Preferences tab structure outside of PreferencesModel's supported tabs — ordinary customPreferences content does not need to touch PreferencesPanel at all, since it's nested inside whichever built-in panel (Simulation/Visual/Audio/Input/Localization) you attached it to.

PreferencesTabs and PreferencesTab

PreferencesTabs is the HBox of tab buttons across the top of the dialog (implemented as role="tablist"/role="tab" for accessibility, with arrow-key navigation between tabs built in); PreferencesTab is a single tab button within it, associated with one PreferencesType value.

ts
new PreferencesTabs(
  supportedTabs: PreferencesType[],
  selectedPanelProperty: TProperty<PreferencesType>,
  providedOptions: PreferencesTabsOptions // tandem is required
)

new PreferencesTab(
  labelProperty: TReadOnlyProperty<string>,
  property: TProperty<PreferencesType>,
  value: PreferencesType,
  providedOptions: PreferencesTabOptions // tandem is required
)

PreferencesTabs builds one PreferencesTab per entry in supportedTabs that matches a known PreferencesType (OVERVIEW, SIMULATION, VISUAL, AUDIO, INPUT, LOCALIZATION — the LOCALIZATION tab additionally gets a globe icon). PreferencesTabs.focusSelectedTab() (surfaced on PreferencesDialog too, see PreferencesModel and PreferencesDialog) moves keyboard focus to whichever tab is currently selected.

For ordinary custom preferences, you only need PreferencesPanelSection and PreferencesControl

PreferencesModel's customPreferences.createContent callback is expected to return a single Node — nesting your control(s) in PreferencesControl (for label/control/description layout) inside PreferencesPanelSection (for standard section spacing) is enough to match the built-in tabs' look. PreferencesPanel/PreferencesTabs/PreferencesTab are the dialog's own scaffolding, not something a customPreferences entry needs to construct.