Panels and Backgrounds
Panel and AccordionBox are the two standard ways to give a group of controls a visible background. This page is about when to reach for which, and how their appearance options should be set consistently across a project — not their full option lists.
Panel for always-visible groups, AccordionBox for optional ones
Use Panel when the controls inside should always be visible — a persistent control panel in a screen's corner. Use AccordionBox when the content is secondary or optional and the user should be able to collapse it to reclaim space:
import { Panel, AccordionBox } from 'scenerystack/sun';
import { VBox, Text } from 'scenerystack/scenery';
// Always visible
const controlPanel = new Panel( controlsVBox, {
fill: '#f0f0f0',
stroke: 'black'
} );
// Collapsible, secondary content
const graphAccordionBox = new AccordionBox( graphContent, {
titleNode: new Text( 'Graph' ),
expandedDefaultValue: false
} );Both take exactly one content Node — build the actual controls with a layout container (VBox/GridBox) first, then wrap that in the Panel/AccordionBox.
One appearance, reused everywhere
A project should settle on one fill/stroke/cornerRadius/xMargin/yMargin combination for its panels and reuse it via a shared options object, rather than repeating literals (or worse, drifting slightly) at every call site:
// MySimConstants.ts
const MySimConstants = {
PANEL_OPTIONS: {
fill: '#f0f0f0',
stroke: 'black',
lineWidth: 1,
cornerRadius: 8,
xMargin: 10,
yMargin: 8
}
};
// Usage
import { combineOptions } from 'scenerystack/phet-core';
const controlPanel = new Panel( controlsVBox, MySimConstants.PANEL_OPTIONS );
const wideControlPanel = new Panel( otherControlsVBox,
combineOptions( {}, MySimConstants.PANEL_OPTIONS, { minWidth: 220 } )
);combineOptions (from scenerystack/phet-core) merges a base options object with per-instance overrides without mutating the shared constant.
Route panel fills through a colors file
Don't hard-code fill: '#f0f0f0' inline — pull it from the project's color profile file so the panel automatically adapts to projector mode:
import MySimColors from './MySimColors.js';
const controlPanel = new Panel( controlsVBox, {
fill: MySimColors.panelFillProperty,
stroke: MySimColors.panelStrokeProperty
} );Panel's fill/stroke accept a Property<Color> directly (like any scenery paint), so the panel updates live when the profile switches.
Nesting panels sparingly
A Panel inside another Panel reads as visual clutter — two borders and two background tints stacked for no additional grouping information. If a sub-group truly needs separation, prefer a HSeparator/VSeparator line or extra spacing within a single outer Panel over nesting.
Match cornerRadius across the sitewide component set
Panel defaults to cornerRadius: 10; sun's buttons and other components have their own default corner radii. If a project's panels use a different radius than its buttons, the mismatch reads as an inconsistency even when nothing else changed — pick one radius (or a small set: e.g. panels slightly more rounded than buttons) and apply it everywhere via shared constants, as above.