Skip to content

LevelSelectionButtonGroup

LevelSelectionButtonGroup (from scenerystack/vegas) is a Node that builds a whole set of LevelSelectionButtons from a declarative array of items, rather than you constructing and laying out each button by hand. It handles three things a level-select screen otherwise has to duplicate: instantiating one button per item, giving every button in the group a uniform buttonWidth/buttonHeight, and arranging them (a single horizontal row by default, via an internal FlowBox).

ts
import { LevelSelectionButtonGroup, type LevelSelectionButtonGroupItem } from 'scenerystack/vegas';
import { NumberProperty } from 'scenerystack/axon';
import { Text } from 'scenerystack/scenery';
import { Tandem } from 'scenerystack/tandem';

const level1Score = new NumberProperty( 0 );
const level2Score = new NumberProperty( 0 );

const items: LevelSelectionButtonGroupItem[] = [
  { icon: new Text( '1', { fontSize: 40 } ), scoreProperty: level1Score },
  { icon: new Text( '2', { fontSize: 40 } ), scoreProperty: level2Score }
];

const buttonGroup = new LevelSelectionButtonGroup( items, {
  levelSelectionButtonOptions: {
    listener: () => { /* look up which level was pressed via buttonGroup.buttons */ }
  },
  groupButtonWidth: 120,
  groupButtonHeight: 120,
  gameLevels: [ 1, 2 ], // value of the sim's gameLevels query parameter
  tandem: Tandem.REQUIRED.createTandem( 'levelSelectionButtonGroup' )
} );

Constructor

ts
new LevelSelectionButtonGroup( items: LevelSelectionButtonGroupItem[], providedOptions?: LevelSelectionButtonGroupOptions )

items must be non-empty, ordered by increasing level number (level 1 first) — tandemNames and the default level${N}Button naming both assume that ordering.

LevelSelectionButtonGroupItem

FieldRequiredDescription
iconyesThe icon Node for this level's button
scorePropertyyesReadOnlyProperty<number> shown on the button
tandemNamenoOverrides the default level${N}Button tandem name
optionsnoPer-button LevelSelectionButtonOptions overrides (everything LevelSelectionButtonOptions accepts except tandem/buttonHeight/buttonWidth, which the group controls)

Options

OptionDefaultEffect
levelSelectionButtonOptionsOptions applied to every button in the group; a specific item's options overrides these
flowBoxOptions{ orientation: 'horizontal', spacing: 10 }Options for the default FlowBox layout; ignored if createLayoutNode is provided
createLayoutNodedefault FlowBox builder( buttons: LevelSelectionButton[] ) => LayoutNode — supply this for a custom arrangement (multiple rows, a grid, etc.) instead of the default single row
gameLevelsrequiredArray of positive integers (1-based) — only buttons whose level number appears in this array are made visible. All buttons are still constructed (so the PhET-iO API doesn't change conditionally), just hidden. Set this from your sim's gameLevels query parameter
groupButtonWidth / groupButtonHeightUniform size applied to every button via LevelSelectionButtonOptions.buttonWidth/buttonHeight

Public API

MemberDescription
buttonsThe constructed LevelSelectionButton[], ordered by increasing level number
focusLevelSelectionButton( level )Moves keyboard focus to the button for the given 1-based level number — call this when returning to the level-select UI (e.g. after a "Back" or "Start Over" action) so keyboard traversal resumes somewhere sensible

Build a custom layout with createLayoutNode, not by bypassing the group

If a single row doesn't fit your design (multiple rows, an irregular grid), provide createLayoutNode rather than constructing LevelSelectionButtons yourself — you still get the group's uniform sizing, gameLevels visibility handling, and buttons/focusLevelSelectionButton() API for free.

gameLevels is required, and levels are 1-indexed

Unlike most options, gameLevels has no built-in default — omitting it (in TypeScript) is a type error, and passing an empty array asserts. Level numbers start at 1 to match the convention used by the gameLevels query parameter, not 0.