The problem
A footer is a claim about what commits together. Put one at the bottom of a surface and it says: this is the boundary of the thing, and this is what you do with it. The claim is made by placement, before any label is read. The mistake is not having footers inside containers. It is a footer whose boundary does not match the changes it saves. When a view’s whole purpose is configuration, every group of fields on it looks like a form — so every group grows a footer, and the page makes the same claim three times over a single set of changes. The labels then have to carry the scope that position no longer distinguishes: “Save account profile”, “Save notifications”. A reader who edited both has two commits to remember, in an order nobody specified, and no answer to “am I done?”The solution
One question decides every footer: what commits together, and which surface bounds it? The set of changes that succeed or fail as one is the unit of commit. The footer belongs to the surface that bounds that unit, and that surface is the only one on screen that gets one.
Between the first two rows, the test is what the view is for, not how it is built. Both are
cards full of fields; they differ in whether the configuration is the view.
Whichever surface owns the footer, the others have none. A container footer on a view that
also commits those same fields is two claims over one set of changes, which is the shape at the
top of this page.
Nesting is not a second claim. A dialog or drawer opened over a view owns its footer for as long
as it is open; the view’s footer beneath it is inert and covers different changes.
Vocabulary
A region at the bottom of a container is not automatically a footer. A panel footer holding
a row count, a last-updated timestamp, or a link into the object it describes is a summary
region. It becomes a footer in this page’s sense only when it holds a commit.
When this applies
- A set of changes on one surface commits or discards together.
- The surface is a view, a container within a view, a dialog, or a drawer.
- The reader needs one place to say “apply what I changed” and one to say “never mind”.
When it doesn’t
Structure
The footer’s composition, in order, whichever surface owns it:
The trailing pair is tertiary then primary: a text-styled Cancel beside a filled commit.
A footer’s contents align to the gutter of the surface that owns it — the same inset that
surface’s own content already uses. A footer inset differently reads as a separate surface
floating over the content rather than that content’s last row.
Only the container case aligns on its own. On the other three, set the inset to match the
surface’s own gutter rather than accepting what ships.
Behavior
The happy path
- The reader opens the surface. Its footer is present from the first paint, with nothing changed yet.
- The reader edits. Each field validates on its own terms — the footer does not change what a field does.
- The reader presses the commit. The unit commits as one gesture. One gesture is not one request: a view whose sections map to separate resources may still issue a request per changed section, and a section with nothing changed is not re-sent.
- On success, a view or container stays where it is — neither is a flow and neither has a next page. A dialog or drawer closes. The result is announced either way, and the footer returns to its nothing-changed state.
Edge and failure states
Content
The commit label states the action. The surface states the scope. A scope inside a commit label exists only to distinguish it from a competing commit on the same screen. Once one footer owns the unit there is nothing to distinguish, and a section-named commit is actively wrong — it implies the other sections were left out.
Section headings and their descriptions carry the scope the buttons used to — “Account profile”,
“Who this account is and how it is labeled across reports.” That is where scope belongs, because
it is read on the way in rather than at every commit.
Accessibility
Focus. The footer is last in the DOM within its owning surface, matching its visual position. On a failed commit, focus moves to the first invalid field — not back to the footer and not nowhere. Focus never lands on a control the fixed footer is covering, which is a risk a fixed footer introduces and a static one does not. Announcement. The result of a commit is announced, not only drawn. A reader who cannot see the confirmation has no other signal that a save succeeded, because nothing about the surface moved. The commit’s nothing-changed and in-flight states are carried by the button’s own state, never by color alone. Landmark. A view-level footer carries thecontentinfo landmark, one per page — set it on
the region yourself, because the shipped region declares the wrong one. A container-level footer
is not a landmark; it is part of the container’s own content.
Constraints
Variations
A center-aligned and a left-aligned arrangement also render. Neither has a use case on any
surface this page governs; reach for one of the five above.
Anti-patterns
Known issues
Where the shipped components and this direction disagree. Build to the direction above.Gaps in the current rules
Titan does not specify these. Asked about one, the answer is “the system does not specify this.”- What happens when one section is invalid and another is valid, under a view-level footer. Two answers are available and neither is chosen: commit the valid sections and report the invalid one, or block the whole commit until the view is valid. The first keeps an edit the reader had already finished and makes “Save changes” partly true; the second is simpler to build and loses that edit.
- What Cancel discards when several sections changed. Whether it reverts the whole unit silently, reverts it behind a confirmation, or is withdrawn once the surface is dirty enough that discarding is the bigger mistake.
- Whether the unavailable commit is disabled, and whether the footer says why.
- A view with two genuinely independent units of commit — not sections of one subject, but two subjects on one page. One footer per unit is the rule; nothing says what to do when the changes really are disjoint.
- When a footer should carry a save-state line, and what it says.
- Where a fixed footer sits on a narrow or short viewport, and whether it stays fixed when it would cover a meaningful share of the content.
Why it works this way
Position is the only thing a footer says about its own scope. A control at the bottom of a surface reads as “this is the end of the thing, and this is what you do with it.” That claim is free and unavoidable — it is made by placement, before any label is read. So the surface holding the footer must be the surface whose changes it saves, and no screen can make the claim twice without the reader having to re-derive which one means what. The container case is not an exception to the view case. They are one rule reaching two answers, because the unit of commit sits in a different place. A view that is entirely configuration has one unit spanning every section; a view that reads, summarizes, and happens to contain one configurable container has its unit inside that container. Asking what commits together answers both without a second rule. Fixed at the view level, because configuration is edited in the middle. A form is filled top to bottom and ends where its footer is, so a static footer is in the right place by construction. A configuration view is entered to change one section, which may be anywhere in it, and a footer at the end of the scroll is a journey away from the edit. A container footer needs none of this: it is already beside the fields it commits. The sections keep their dividers. The rule removes the commits, not the boundaries. A section still has to look like a bounded group of related fields, because that grouping is how a reader finds the one they came for.Related
- Views overview — the frame’s footer region, its slots, and the arrangements it ships
- Settings view — the archetype whose whole purpose is configuration
- Full-page form · Wizard · Detail view — the other archetypes that carry, or deliberately omit, a footer
- Panel · Dialog · Drawer — the containers that own footers of their own
- Form validation — what a field does when the commit is pressed
- Inline editing — the alternative when the unit of change is one field
- Error handling · Notifications — reporting a failed or successful commit
- Button — prominence and trailing-primary order
- Elevation — the shadow and stacking a fixed region needs
- Progressive disclosure — when a section starts collapsed