Skip to content

Customise the picker

Every knob on the ready-made Flutter picker: theme modes, colour schemes, style slots, chrome switches, layout sizes, strings, builders, and callbacks.

Updated View as Markdown

This is rung 2 of the three ways in: the same SeatLayerPicker widget, changed to look and read like your product. Nothing here requires you to lay out a single widget.

Four depths of customisation on the same widget: recolour everything at once from a theme or ColorScheme, restyle one control through a style slot, hide or resize a part through chrome options and layout sizes, or replace one part entirely through a builder slot.

Every parameter below is accepted by SeatLayerPicker, SeatLayerPickerPage, showSeatLayerPicker, and SeatLayerPickerScope alike.

The parameters at a glance

Parameter Type Default What it changes
configuration SeatLayerConfiguration required Event, credentials, currency
onCheckout SeatLayerCheckoutCallback required Where the hold is handed off
themeMode SeatLayerThemeMode .auto Light or dark side
theme SeatLayerPickerThemeData? null Colours, radii, typeface, sizes, style slots
options SeatLayerPickerOptions const …() Behaviour, chrome switches, strings, haptics
builders SeatLayerPickerBuilders const …() Replacements for individual parts
callbacks SeatLayerPickerCallbacks const …() Everything that happens, reported
controller SeatLayerPickerController? null Drive the picker from your own code
onClose VoidCallback? null The buyer dismissed it

Recipes

Each recipe is complete on its own. Combine as many as you need.

Walkthrough: a branded native picker without rebuilding inventory logic

This page is for a Flutter buyer surface that should look like the host app while retaining SeatLayer’s live selection, hold, recovery, and checkout handoff. It composes ordinary Flutter chrome around the shared SeatLayer venue renderer; it is not a fully native replacement for the venue engine.

branded_ticket_screen.dartdart
import 'package:flutter/material.dart';
import 'package:seatlayer/seatlayer.dart';

Future<void> openHostCheckout(String holdId) async {
  // Post this opaque ID to a trusted server; do not post browser prices or seats.
  debugPrint('Create checkout for hold $holdId');
}

class BrandedTicketScreen extends StatelessWidget {
  const BrandedTicketScreen({super.key, required this.eventKey});

  final String eventKey;

  @override
  Widget build(BuildContext context) => SeatLayerPicker(
        configuration: SeatLayerConfiguration(
          event: eventKey,
          publicKey: '<YOUR_PUBLIC_KEY>',
        ),
        themeMode: SeatLayerThemeMode.auto,
        theme: const SeatLayerPickerThemeData(
          accent: Color(0xFFE54558),
          radius: 16,
        ),
        options: const SeatLayerPickerOptions(
          chrome: SeatLayerPickerChromeOptions(showPriceRail: false),
          strings: SeatLayerPickerStrings(holdAndCheckout: 'Continue'),
        ),
        builders: SeatLayerPickerBuilders(
          confirmCard: (context, part) => DecoratedBox(
            decoration: const BoxDecoration(color: Color(0xFFFFF7F7)),
            child: part.defaultChild,
          ),
        ),
        onCheckout: (handoff) => openHostCheckout(handoff.holdId),
      );
}

Replace the proof-only debugPrint in openHostCheckout with the app’s trusted backend call. That backend inspects the hold, calculates the payable total, takes payment, and books with its stable bookingRef. Do not change client display formatting or the confirmation card into a source of inventory or price truth. For a layout that moves whole native parts, continue to build your own layout; the map, one picker state, and the hold contract remain shared.

Brand it from your app’s colours

SeatLayerPicker(
  configuration: configuration,
  theme: SeatLayerPickerThemeData.of(context),
  onCheckout: openCheckout,
)

.of(context) reads your Theme.of(context).colorScheme and body typeface; .fromColorScheme(scheme) takes a scheme you build yourself. Either way primary and onPrimary become the accent and its ink, so Continue, Select, Find N best seats, the hold pill, and the Map/3D control all carry your brand at once, while surface, onSurface, onSurfaceVariant, outlineVariant, and error become the grounds, ink, hairlines, and failures.

Because a ColorScheme is a complete ground palette, this pins the picker to the side that scheme is on. That is what you want, since it came from your theme, and .of(context) follows your own dark-mode switch on rebuild.

Brand it with one colour, and keep following the device

SeatLayerPicker(
  configuration: configuration,
  themeMode: SeatLayerThemeMode.auto,
  theme: const SeatLayerPickerThemeData(accent: Color(0xFFE54558)),
  onCheckout: openCheckout,
)

The default constructor sets only the roles you name, so every ground role still comes from themeMode. Use it when you want a brand accent and live light and dark.

Square off one button, keep the rest

theme: SeatLayerPickerThemeData.light(
  styles: SeatLayerPickerStyles(
    continueButtonStyle: FilledButton.styleFrom(
      shape: const RoundedRectangleBorder(),
    ),
  ),
),

Or restyle that one instance where it is placed, which wins over the theme:

SeatLayerDockBar(
  style: const SeatLayerSurfaceStyle(shape: RoundedRectangleBorder()),
)

Hide the price legend

options: const SeatLayerPickerOptions(
  chrome: SeatLayerPickerChromeOptions(showPriceRail: false),
),

Replace the confirm card with your own

builders: SeatLayerPickerBuilders(
  confirmCard: (context, part) => MyConfirmCard(
    state: part.state,
    controller: part.controller,
    fallback: part.defaultChild,
  ),
),

The builder receives the immutable state, the live controller, and the widget the drop-in would have rendered, so you can decorate the default, or ignore it and draw your own, calling part.controller.selectObjects([...]) yourself.

Speak the buyer’s language

options: SeatLayerPickerOptions(
  strings: SeatLayerPickerStrings.forLocale(Localizations.localeOf(context)),
),

Reword one line

options: SeatLayerPickerOptions(
  strings: SeatLayerPickerStrings(
    holdAndCheckout: 'Réserver et payer',
    seatsLeft: (count) => '$count restants',
  ),
),

Ship dark only

SeatLayerPicker(
  configuration: configuration,
  themeMode: SeatLayerThemeMode.dark,
  theme: const SeatLayerPickerThemeData.dark(accent: Color(0xFFFF5A6F)),
  onCheckout: openCheckout,
)

Everything at once

SeatLayerPicker(
  configuration: configuration,
  themeMode: SeatLayerThemeMode.auto,
  theme: SeatLayerPickerThemeData.light(
    accent: const Color(0xFFE54558),
    onAccent: Colors.white,
    radius: 14,
    buttonRadius: 0,
    fontFamily: 'YourSans',
    layout: const SeatLayerPickerLayout(dockBarHeight: 60),
    styles: SeatLayerPickerStyles(
      continueButtonStyle: FilledButton.styleFrom(
        shape: const RoundedRectangleBorder(),
      ),
    ),
  ),
  options: SeatLayerPickerOptions(
    chrome: const SeatLayerPickerChromeOptions(showPriceRail: false),
    strings: SeatLayerPickerStrings.forLocale(Localizations.localeOf(context)),
  ),
  builders: SeatLayerPickerBuilders(
    cartSheet: (context, part) =>
        MyOwnSheet(state: part.state, fallback: part.defaultChild),
  ),
  callbacks: SeatLayerPickerCallbacks(
    onSeatSelected: (seat) => analytics.log('seat', seat.label),
  ),
  onCheckout: openCheckout,
)

Theme modes

themeMode takes SeatLayerThemeMode.auto, .light, or .dark. The resolved theme updates the native controls and drawn venue together while keeping the selection, focused section, and camera. There is no reload.

Precedence: an explicit themeMode, then your app’s theme, then the device. From 0.3.1, auto reads Theme.of(context).brightness first, the switch a buyer actually moves inside your app, which a Cupertino theme reports too. It falls back to MediaQuery.platformBrightness only where there is no Material or Cupertino theme above the picker. Both readings are live.

SeatLayerPickerCallbacks.onThemeResolved reports the side that won.

From 0.3.1 the picker also dresses the device’s status and navigation bars from the resolved palette, because that surface is the picker’s, and forces the dark style for the immersive 3D scene whatever side the picker is painted on. Opt out with SeatLayerPickerChromeOptions(manageSystemOverlays: false); seatLayerPickerOverlayStyle(resolvedTheme) is exported so a host that opts out can still ask what the picker would have set.

SeatLayerPickerThemeData

Every field is optional. Unset roles come from the resolved mode.

Field Type Role
accent Color? Primary actions, hold pill, active controls
onAccent Color? Ink on the accent
background Color? The recessed page behind the chrome
surface Color? Cards, sheets, bars
text Color? Primary ink
mutedText Color? Secondary ink
divider Color? Hairlines
error Color? Failures and recoverable errors
warning Color? Commercial warnings on ticket lines
fontFamily String? The chrome’s typeface
radius double? Cards, sheets, surfaces
buttonRadius double? Every action, as its own role
logo ImageProvider? Header mark
mapTheme SeatLayerMapThemeData? The drawn canvas, row labels, selection ring
layout SeatLayerPickerLayout? Every size
styles SeatLayerPickerStyles? Per-element style slots

Constructors: the default one above, the .light() and .dark() presets, .fromColorScheme(scheme), and .of(context).

buttonRadius is its own role rather than a fraction of radius, so SeatLayerPickerThemeData.light(radius: 20) rounds the cards and sheets without growing pill actions. Actions default to 8 logical pixels, which is what the web picker’s own buttons measure; exactly three things stay true pills: the hold countdown, the price-legend chips, and the Map/3D segmented control.

Style slots

Slot Type Applies to
primaryButtonStyle ButtonStyle? Select, Find N best seats, primary actions
secondaryButtonStyle ButtonStyle? Cancel, Back to venue, secondary actions
continueButtonStyle ButtonStyle? Continue and Hold seats & checkout
iconButtonStyle ButtonStyle? Round map and stepper controls
chipShape OutlinedBorder? Chip shape across the picker
legendChipStyle SeatLayerSurfaceStyle? Price-legend chips
floorStripStyle SeatLayerSurfaceStyle? Floor chips
dockBarStyle SeatLayerSurfaceStyle? The dock under the map
confirmCardStyle SeatLayerSurfaceStyle? The seat decision card
sheetStyle SeatLayerSurfaceStyle? The cart sheet
headerStyle SeatLayerSurfaceStyle? The header line
pillStyle SeatLayerSurfaceStyle? The hold countdown pill
seatViewChromeStyle SeatLayerSurfaceStyle? The seat-view caption strip

Button slots take a Material ButtonStyle. Surface slots take a SeatLayerSurfaceStyle, whose fields are color, shape, elevation, padding, and textStyle. Every widget that owns a slot also accepts a style: parameter, which wins over the theme for that one instance.

SeatLayerPickerLayout

Every number in the design is a default, not a constant.

Field Default Field Default
phoneBreakpoint 640 confirmCardGutter 12
wideBreakpoint 840 confirmCardMaxWidth 310
headerHeight 38 confirmIdentityHeight 42
headerLogoSize 22 confirmPhotoHeight 64
topRailHeight 44 confirmActionHeight 44
dockBarHeight 52 selectorHeight 44
peekHeight 58 accessibilityControlSize 36
sheetMaxHeightFraction 0.72 mapControlSize 36
emptyTrayMaxHeight 380 attributionHeight 18
cartCardMinHeight 52 legendChipFontSize 11

These are the 0.12.0 defaults. The class has more fields for the cart sheet, its handle, and the seat card; see the tagged layout source for the full list. denseLineHeight and denseVisibleLines were removed in 0.9.0 along with the dense ticket list: the cart now shows one card per ticket.

SeatLayerPickerChromeOptions

Switch Default Hides
showHeader true The event header line
showPriceRail true The price legend
showFloorSelector true The wide-layout floor selector
showFloorStrip true The phone floor chips
showMapControls true All corner map controls
showOverviewControl auto The explicit overview button
showZoomControls true The zoom controls
showZoomToFitControl true Show whole venue
showViewModeControl true The Map/3D segmented control
showColorblindControl auto The colorblind toggle
showAccessibilityControl true The accessibility sheet
showTicketPanel true The cart panel
showDockBar false The dock under the map
showConfirmCard true The seat decision card
showVenue3DChrome true The 3D scene’s own chrome
showSeatViewChrome true The native seat-view caption
showHoldPill true The hold countdown
showExtendHoldPrompt auto The “Need more time?” card in the last minute of a hold
seatCardGlass true The frosted veil behind the seat card
manageSystemOverlays true Status and navigation bar styling

The switches marked auto default to hidden on a phone and shown on a wide layout: the colorblind palette sits inside the phone’s accessibility sheet, and the phone shows one hold timer in the header. Since 0.9.1 no layout draws the dock bar unless you set showDockBar: true. Pass an explicit boolean to override any default.

Hiding showSeatViewChrome means the host owns that disclosure itself; the SDK does not add a second copy behind the custom interface.

SeatLayerPickerOptions

Field Type Default Meaning
layout SeatLayerPickerLayoutMode .adaptive Force .compact or .wide
holdTtl Duration? null Request a hold window
initialHoldId String? null Restore a host-owned hold
readOnly bool false Inspect without changing inventory
confirmSelection bool true Require the confirm card
enableBestAvailable bool true Offer Best Seats
enable3D bool true Offer the immersive venue scene
enableSeatView bool true Offer the view from a seat
max3DSeats int? null Cap 3D by seat count
hideEventDetails bool false Drop the event identity line
eventName String? null Header title when you want your own
panelInitiallyCollapsed bool true Open on the peek
persistColorblindPreference bool true Remember the palette choice
refreshOnResume bool true Recheck availability when the app returns
announceHoldLapse bool true Tell the buyer when a hold lapses
showBookedOverlay bool true Show the booked state after the sale
chrome SeatLayerPickerChromeOptions const …() The switches above
languages List<Locale> const [] Offer a language switch
pricing SeatLayerPickerPricing? null Price display overrides
strings SeatLayerPickerStrings const …() Every buyer-facing word
haptics bool true Feedback on selection and expiry

Strings and locales

SeatLayerPickerStrings is a plain value class: every buyer-facing word is a field you can override. Most are String: close, chooseSeats, overview, cancel, select, viewFromHere, venue3D, openVenue360, backToVenue, bestSeats, holdAndCheckout, accessibility, fitVenue, fitWholeVenue, mapView, retry, notAvailable, undo, allFloors, and more. A few are functions, because they take a value:

final String Function(int count) ticketCount;
final String Function(int count) seatsLeft;
final String Function(int count) seatsFree;
final String Function(int count) findBestSeats;
final String Function(int count) moreCount;
final String Function(String money) fromPrice;
final String Function(String money) continueWithTotal;
final String Function(String clock) heldFor;
final String Function(List<String> parts) seatIdentity;

accessNeeds is a Map<String, String> of access-need labels.

SeatLayerPickerStrings.forLocale(locale) returns the same reviewed wording the drawn map uses, across 37 languages. It resolves by language, and by script for Chinese. An untranslated locale keeps its English default, as does any single entry the SDK has no wording for, and the result is an ordinary SeatLayerPickerStrings, so you can still override any entry on top of it:

final strings = SeatLayerPickerStrings.forLocale(
  Localizations.localeOf(context),
);
// then override just the one line you word differently

allFloors is the one string the SDK has no dictionary entry for and keeps English in every locale until it does. Override it if you ship a multi-floor venue in another language.

Motion and haptics

Every animation spends a duration from SeatLayerPickerMotion, for example enter, exit, dock, sheet, fly, pop, bump, crossfade, toast, and immersive. Every one of them collapses under MediaQuery.disableAnimations, so a buyer’s reduced-motion preference is honoured without a setting of yours.

Haptics are one switch, SeatLayerPickerOptions(haptics: false). The cues are PickerHapticCue.selectionAdded, .sectionFocused, .holdCreated, and .holdExpired; the last fires from SeatLayer’s expiry signal rather than a local timer.

Callbacks

All optional, all on SeatLayerPickerCallbacks.

Callback Payload Fires when
onReady ReadyInfo The chart is ready
onChartLoad SeatLayerChartLoad One chart load finished, for your own analytics
onSelectionChanged List<SelectedSeat> The selection changes
onSelectionValidityChanged SelectionValidity Validation state changes
onSeatSelected SelectedSeat A seat is added
onSeatRemoved String A seat is removed
onSectionFocused String A section is focused
onSeatViewOpened SelectedSeat The view from a seat opens
onHoldChanged hold and handoff A hold is created or replaced
onHoldExpired None The server expired the hold
onBooked SeatLayerCheckoutHandoff The handed-off hold settled to booked
onContinue SeatLayerCheckoutHandoff Checkout is confirmed
onThemeResolved Brightness The resolved side changes
onAccessExpired BuyerAccessExpiredEvent Buyer access lapsed
onAccessUnavailable BuyerAccessUnavailableEvent Access could not be granted
onSelectedObjectUnavailable SelectedObjectUnavailableEvent A held seat was taken
onError SeatLayerError A recoverable action failed
onClosed SeatLayerPickerCloseReason The picker closed

Builder slots

Each takes Widget Function(BuildContext, SeatLayerPickerPartContext), and the part context carries state, controller, and defaultChild.

header, priceRail, legend, sectionNavigator, floorStrip, dockBar, accessibilityFilters, map, mapControls, bestAvailable, seatConfirmation, confirmCard, generalAdmissionPrompt, tablePrompt, selectionTray, cartSheet, venue3D, seatViewChrome, holdCountdown, actionError, checkoutBar, loading, error, and empty.

The overall adaptive layout, the test marker, and the required attribution deliberately have no builder: theme colours and typography stay customisable, but required chrome cannot be hidden by returning an empty widget.

Next steps

Navigation

Type to search…

↑↓ navigate↵ selectEsc close