Frame
Boxes a region once and divides it by rules.
Styles
wh.orders_daily
96 partitions2026-10-19148,201 rows2026-10-18148,201 rows2026-10-17148,201 rowsframedefault
wh.orders_daily
96 partitions2026-10-19148,201 rows2026-10-18148,201 rows2026-10-17148,201 rowspanel
Header
orders-daily
Job failed02:14 UTCThe source dropped a column. The next run starts at 03:00.
2026-10-19148,201 rows2026-10-18148,201 rows2026-10-17148,201 rowsstate
Usage
When to use it
- Use
- A region of a page: a table, a log, a pipeline's stages, a group of settings.
- Use
- A raised summary standing on its own beside the main region (variant="panel").
- Not for
- Parts inside a region; divide them with rule-row and rule-col. Use Collapsible.
- Not for
- Something floating over the page. Use Popover.
Rules
7 rules
- Never
- Put a Frame or a panel inside a Frame. Because rules carry Sodium's layout, and a box in a box draws two edges where one closes the region.
- Must
- Put the thing's name in FrameTitle as written, then its state in FrameStatus and its meta in FrameValue meta; never a kind word before the name. Because a product header reads as the name and then how it is, and a KIND prefix in capitals reads like a spec sheet and says what the body already shows.
- Should
- Give each Frame an accessible name, with aria-label or aria-labelledby on its FrameTitle, when a view holds more than one. Because a section with no name is not a landmark, so a screen reader cannot tell the regions apart or jump between them.
- Must
- Hold at most one primary Button in a frame, header and footer together. Because a frame is one group of actions, and the slab marks the one thing to act on there.
- Should
- Use padding="none" for rows and tables, so they run to the frame's edge. Because a row's rule should meet the frame's edge, not stop 8px short of it.
- Must
- Start a body with its data, or one FrameDescription sentence, never a headline; divide a second part with a second FrameBody. Because the header already names the thing and its state, so a 24px headline under it repeats them in a marketing voice, and one rule between parts is what makes two regions read alike where a hand-set border drifts.
- Should
- Use 26px buttons in the header and footer: icon-sm ghost buttons named with aria-label in the header, size="sm" in the footer. Because the header is a 38px row (--head), where a 26px ghost leaves 6px clear and its glyph lands 12px from the edge, and the footer's 12px inset puts a 26px button's box as far from the rule as from the side.
Accessibility
3 checks
- A section; give it an accessible name with aria-labelledby on the FrameTitle's id when the view has several.
- The edge is --line, the same rule a field, a menu and a secondary button draw; it divides, it is not a cue.
- The header's name is text-1 on the panel at 17.6:1 and 14.9:1; its meta text-2.
Edge cases
4 edge cases
- Long title
- Truncates before the actions move.
- Phone, 390 wide
- The frame fills the width; footer actions wrap under the meta.
- Dark theme
- Rules step up from the near-black page; the panel variant is a step lighter.
- Forced colors
- The edge draws in CanvasText.
API
7 props
Frame variant'frame' | 'panel'default'frame'FrameBody padding'default' | 'none'default'default'FrameDescriptionpFrameHeader, FrameTitle, FrameActionsheader, h2, divFrameStatusspanFrameValue metabooleandefaultfalseFrameFooter, FrameMetafooter, span
Tokens
13 tokens
--page- #ffffff
--panel- #fafaf7
--line- #e2dfd5
--line-faint- #efede6
--line-width- 1px
--head- 38px
--row- 40px
--inset- 12px
--type-code- 400 13px/20px var(--font-mono)
--type-body-sm- 400 14px/20px var(--font-sans)
--type-code-sm- 400 12px/16px var(--font-mono)
--text-1- #161512
--text-2- #55524a