Level: atom.
Purpose and use
wbl-main-container supplies the shared desktop content frame beside Sidebar.
It owns the fixed 12px outer inset, white rounded surface, strict 20px top and
side insets, and the single vertical scrollport for routed page content. Its
container-owned layout rail uses the named size selected by the page layout and
centers it when the available surface is wider. Place Header Widget, tabs,
filters, tables, or another page composition in its one default slot. Do not
use it as a card inside a page or as a replacement for a table's horizontal
scroll wrapper. A wbl-table projected into it follows the shared Table
composition rule below; other projected content remains in the selected rail.
Anatomy and behavior
The outer frame fills the height supplied by the application shell. Its inner
surface has --color-background-level-1-base and --radius-xl; padding uses
--space-300 and --space-500. The keyboard-focusable scrollport remains the
one vertical scrolling owner and contains the internal layout rail around the
default projection. The rail fills the available width up to its selected named
maximum, then uses automatic inline margins to center it; every limit therefore
applies inside the surface side padding. The host exposes the selected size as
data-size for its own layout styling.
The scrollport does not provide horizontal scrolling. Normal projected content
remains in the rail, so the page itself never becomes a horizontal scroll owner.
For every wbl-table projected into this container, the Table's Filter Row, any
rendered Action Bar, and scroll frame/grid remain rail-aligned at every available
width. In contained or paginated mode, any native scrollbar gutter stays inside
the Table viewport; it never changes the viewport's outer size, rail-aligned
edges, or the Main Container rail. This is a Main Container composition rule,
not a second projection slot, screen exception, or Table public API. The Table
keeps its own horizontal viewport and sticky columns.
Each page layout owns one Main Container and chooses its size; projected
children never select or override it. The rail is centered in the working area
to Sidebar's right and therefore naturally follows Sidebar resizing. Modal and
drawer layers live outside this atom. l and xl are layout-level exceptions
that require an explicitly designed page; ordinary current routes use m.
bottomPadding is false by default. When it is true, a static 20px spacer
follows the scrollport inside the surface; this is for short, non-scrolling page
content. It is intentionally omitted for the standard routed screens.
Accessibility and content
With verticalScroll=true, the scrollport has tabindex="0" so keyboard users
can reach it. With false, Main Container removes that tab stop because the
contained Table viewport owns scrolling and focus. The app's
existing <main> remains the only landmark; this atom does not add a landmark
or an accessible name. Content projected into the slot must supply its own
headings, controls, and accessible labels.
Local component contract
This is the owned contract for component.main-container. The implementation,
focused test, export, and Storybook entry below are part of D-038; the layout
regression requirements below must be verified together before review.
Public type values
export type WblMainContainerSize = 'xs' | 's' | 'm' | 'l' | 'xl';
| Value | Maximum inline size | Intended page-layout use |
|---|---|---|
xs |
840px | Small forms and text-focused pages |
s |
1080px | Large forms, onboarding, and settings |
m |
1440px | Default analytics, tenders, and current routes |
l |
2200px | Explicitly designed registries and heavy tables |
xl |
none | Explicitly designed maps, Gantt, or future full-width views |
Classes and selectors
WblMainContainerComponent—wbl-main-container(src/app/design-system/primitives/main-container/main-container.component.ts).
Inputs
WblMainContainerComponent.bottomPadding = input(false)WblMainContainerComponent.size = input<WblMainContainerSize>('m')WblMainContainerComponent.verticalScroll = input(true)
The layout rail and scrollport container are internal. Consumers select a named
size, never an arbitrary CSS length. verticalScroll=false disables Main
Container overflow-y, removes its tabindex, and stretches the internal rail
to 100% of the available height.
Models
None.
Outputs
None.
Slots and projection markers
- One default Angular content-projection slot, wrapped by the container-owned layout rail inside the vertical scrollport. The rail is internal anatomy, not a configurable slot or a public selector.
Layout regression requirements
- At an available surface width at or below the selected size's maximum, normal projected content uses the available width inside the surface padding.
- Above a selected
xs,s,m, orlmaximum, the rail uses that maximum and remains centered.xlhas no maximum. - For every projected
wbl-table, Filter Row, any rendered Action Bar, and the Table scroll frame/grid share the same selected-rail inline edges at every size and viewport width. Table's contained/paginated viewport never expands beyond those edges to reserve a native scrollbar gutter; any such gutter is contained inside the viewport. - The fixed 12px outer inset, existing surface tokens, vertical scrollport,
optional
bottomPaddingspacer, and absence of a Main Container horizontal scroll owner are preserved. The Table owns horizontal overflow and fixed edges where its grid requires them; projected children do not gain a breakout layout from this rule.
Local evidence
- Code:
src/app/design-system/primitives/main-container/main-container.component.ts. - Focused test:
src/app/design-system/primitives/main-container/main-container.component.spec.ts. - Public export:
src/app/design-system/index.ts. - Storybook:
storybook/stories/main-container.stories.ts(Design System/Primitives/wbl-main-container).
Figma status
The current layout-policy evidence is
21918:41024.
It does not establish a published component key or native variable binding, so
no published assembly sidecar is recorded yet; a key must be read before one is
added.
Provenance
The current local implementation, focused test, public export, Storybook entry, and the exact Figma node above are the active evidence. Historical migration artifacts do not define its API.
Fixed-header composition
Transport Requests passes [verticalScroll]="false", which keeps Header Widget,
tabs, and Table's Filter Row visible while the Table rows viewport owns vertical
scrolling. Existing consumers keep Main Container as their vertical scrollport
through the true default. The current implementation, focused tests, and
TableContainedWithinRail Storybook story verify both modes.