← Back to Component Index

Grid Container Component Figma: MEC "row" frame pattern v1 · card-btn only · phone

Audited: 2026-06-26

Structural Properties Specification — Track A Scaffold
ViewModel: GridContainerViewModel : ContainerSurfaceViewModel : ComponentViewModelD26
Design sourced from Figma node gpO3masyyNNHxjvRtdcRo7 · 20785:1418 (My MEC screen — UAL Home) · 2026-06-23. The grid is an ALPA-defined scaffold — there is no single published Figma component_set for it; it is composed from stacked "row" frames each containing card-btn instances. Decision: D25.

1. Scope — What This Spec Covers

v1 covers the exact pattern shown in My MEC: a fixed 3-column grid of equal-width card-btn (ButtonCardViewModel) items on phone. Items are ordered by their position in the collection — the server delivers them pre-sorted. No flexible column count, no mixed content types, no tablet layout in this version.

Defer strategy: the ViewModel type boundary (ObservableCollection<ButtonCardViewModel>) is the intentional constraint. Widening to ObservableCollection<ComponentViewModel> is the extension path when mixed types are needed — the DataTemplate selector already supports this. See § 6 Deferred Extensions.

2. Design Tokens (from Figma — MEC screen, 2026-06-23)

Grid Container

Container width (phone)
361 px
Columns
3
Gap (column & row)
8 px var(--tiny)
Alignment
center / center (flex)
Wrap
flex-wrap

Cell (card-btn in MEC)

Cell height
84 px
Cell width
flex: 1 0 0 (equal thirds)
Cell padding
8 px all sides
Corner radius
8 px

MEC Brand Overrides (theme tokens)

Cell background (MEC)
#12233e
Label text (MEC)
#FFFFFF var(--text/on-brand)
Icon size
24 × 24 px
Label font
League Spartan Medium 12px
Favorite icon
18 × 18 px (top-right)
Note: the dark-navy cell background is a MEC-scoped theme token override, not a grid-level property. The scaffold uses DynamicResource Surface/Brand.

3. Structure Diagram

GridContainerViewModel · 361px · gap 8px PDR DYK QRG DOCUMENTS COMMITTEES UPDATES item[6] item[7] item[8] row 3 auto-created 8px 8px 84 px GridContainerViewModel Columns: int = 3 CellHeight: double = 84 Gap: double = 8 Items: ObservableCollection <ButtonCardViewModel> collection order = display order ButtonCardViewModel (cell) Icon: ImageSource Label: string IsFavorite: bool NavigationTarget: string background via DynamicResource Surface/Brand

4. ViewModel Specification

ContainerSurfaceViewModel (abstract base — D26)

// Shared by all four container ViewModels. BackgroundToken is a string // (not Color) so both XAML and CSS/HTML rendering paths can resolve it // natively. See architecture.html §5 for full dual-target explanation. public abstract class ContainerSurfaceViewModel : ComponentViewModel { // Semantic token name from the Surface/* namespace. // XAML: IThemeResolver.Resolve(BackgroundToken) → Color // CSS: BackgroundToken.ToCssVar() → var(--surface-brand) public string BackgroundToken { get; set; } = "Transparent"; // BorderRadius/* token resolved by IThemeResolver.ResolveRadius() (D38). Fallback: BorderRadius/M (8px). public string? CornerRadiusToken { get; set; } public Thickness Padding { get; set; } = Thickness.Zero; // false = comms team has locked this container's feed position; user cannot reorder (D36). public bool IsSortable { get; set; } = true; protected ContainerSurfaceViewModel() { } protected ContainerSurfaceViewModel(Object? obj) : base(obj) { } }

GridContainerViewModel

public class GridContainerViewModel : ContainerSurfaceViewModel { // Fixed for v1 — see §6 for extension path public int Columns { get; init; } = 3; public double CellHeight { get; init; } = 84; public double Gap { get; init; } = 8; // Collection order drives display order — server pre-sorts public ObservableCollection<ButtonCardViewModel> Items { get; } = new(); // Required constructors (D pattern) public GridContainerViewModel() { } protected GridContainerViewModel(Object? obj) : base(obj) { } }

MAUI View (CollectionView)

<!-- GridContainerView.xaml --> <!-- Outer Border applies the container surface (BackgroundToken → resolved Color via IThemeResolver) --> <Border BackgroundColor="{Binding ResolvedBackground}" StrokeThickness="0" StrokeShape="RoundRectangle {Binding ResolvedCornerRadius}" Padding="{Binding Padding}"> <CollectionView ItemsSource="{Binding Items}" IsScrollEnabled="False"> <CollectionView.ItemsLayout> <GridItemsLayout Orientation="Vertical" Span="{Binding Columns}" HorizontalItemSpacing="{Binding Gap}" VerticalItemSpacing="{Binding Gap}" /> </CollectionView.ItemsLayout> <CollectionView.ItemTemplate> <DataTemplate x:DataType="vm:ButtonCardViewModel"> <views:ButtonCardView HeightRequest="{Binding Source={RelativeSource AncestorType={x:Type vm:GridContainerViewModel}}, Path=CellHeight}" /> </DataTemplate> </CollectionView.ItemTemplate> </CollectionView> </Border>

ResolvedBackground — the view's code-behind (or a IValueConverter) calls IThemeResolver.Resolve(ViewModel.BackgroundToken) to produce the concrete Color. The ViewModel never holds a Color directly. When the MEC theme changes, the resolver re-reads the merged ResourceDictionary and the binding updates.

ResolvedCornerRadius — similarly, IThemeResolver.ResolveRadius(ViewModel.CornerRadiusToken) returns a double pixel value. Falls back to BorderRadius/M (8 px) when CornerRadiusToken is null. See design-tokens.html — BorderRadius/*.

IsScrollEnabled="False" — the grid sits inside the screen's root ScrollView. Nested scroll on the same axis causes gesture conflicts on iOS.

HeightRequest binding — each cell must declare a fixed height because GridItemsLayout does not auto-size rows from content. The default CellHeight=84 matches the MEC design.

5. Factory Mapping

Follow RawRepresentationFactory<T> pattern. The factory receives an ordered list of raw items and maps each to a ButtonCardViewModel. Collection order from the API response is the display order — no client-side sort needed.

API fieldButtonCardViewModel propertyNotes
backgroundTokenBackgroundToken: stringSurface/* token (e.g. "Surface/Brand"); resolved via IThemeResolver at render time (D37)
cornerRadiusTokenCornerRadiusToken: string?BorderRadius/* token (e.g. "BorderRadius/M"); null → fallback 8 px (D38)
isSortableIsSortable: boolfalse locks container position — user cannot reorder it on device (D36). Default true.
iconIcon: ImageSourceResolved via ImageSource.FromUri or local asset key
labelLabel: stringUppercase applied in XAML via TextTransform
isFavoritedIsFavorite: boolDrives heart icon state
navigationTargetNavigationTarget: stringShell route or external URI
array indexcollection positionNo explicit sort field — API ordering is authoritative

6. Deferred Extensions

Do not implement these in v1. Each has a defined extension path that avoids breaking the ViewModel contract.

FeatureTriggerExtension pathBreaking?
Mixed content types Design adds non-card-btn items to a grid section Widen Items to ObservableCollection<ComponentViewModel>; add DataTemplate selector for each type ViewModel API change — factory updated, XAML DataTemplate expanded
Flexible column count A non-3-column grid section appears in design Make Columns a settable property on the factory-built VM; GridItemsLayout.Span binding unchanged No — additive
Responsive / tablet Post-D12 / Epic #2087 tablet pass Add MinCellWidth: double; computed EffectiveColumns from SizeChanged; bind Span to EffectiveColumns No — additive property
Server-driven sort order API needs to reorder items without client changes Add SortOrder: int to DTO; factory sorts before building collection; VM unchanged No — factory-only change
Wide items (ColumnSpan > 1) A full-width button-sm appears inside a grid section Introduce GridItemViewModel wrapper with ColumnSpan: int = 1 + Content: ComponentViewModel; replace Items collection type Yes — ViewModel + factory + XAML all change; isolate behind a feature flag or new VM type

7. MEC Usage Context

The MEC screen uses two independent GridContainerViewModel instances — one for MEC Actions (PDR · DYK · QRG / DOCUMENTS · COMMITTEES · UPDATES) and one for Quick Links (MEC Website · Global Entry · TSA PreCheck / Passport Renewal · ALPA PAC · United PAC). Each is a separate VM bound to its own section feed slot. The section header ("MEC ACTIONS", "QUICK LINKS") is rendered above the grid by the containing screen ViewModel, not by GridContainerViewModel.

MEC instanceItemsRows renderedFigma nodes
MEC Actions62I…;20772:1699 · I…;20772:1739
Quick Links62I…;20915:2965 · I…;20920:3086

Cell background color#12233e in MEC is the MEC brand Surface/Brand token. The ButtonCardView should resolve this via DynamicResource so the same view renders correctly on both the standard ALPA Home (white background) and MEC (dark navy) surfaces without any property override on the grid.

8. Known Constraints