Skip to content

Footer lacks a full-replacement seam #256

@arbrandes

Description

@arbrandes

Description

The footer in frontend-base lacks a full-replacement seam analogous to what the header already has, which makes whole-footer replacements (for example, swapping in tutor-indigo's IndigoFooter) awkward or impossible without forking.

shell/footer/Footer.tsx composes the footer inline: a <footer> wrapper, a column flex container, four column slots with hardcoded inner layouts (LeftLinks, CenterLinks, etc.), and a <PoweredBy> component all live outside any <Slot>, so no slot operation can replace them. The only full-width slot, desktopTop.v1, hardcodes a RevealLinks layout that wraps children in a Collapsible, so a wholesale-replacement plugin gets hidden behind a "more" toggle. The four column slots are too narrow and each carry their own hardcoded layouts, so they are not "drop your footer here" targets either.

The header already solved this. Its desktop/mobile layouts are registered as plain widgets on dedicated slots in shell/header/app.tsx, and Header.tsx is just a two-<Slot> shell. Replacing the layout widget is a one-op operation. The footer should work the same way.

Proposed direction

Introduce one new top-level slot (e.g. org.openedx.frontend.slot.footer.desktop.v1) and one new layout widget (e.g. org.openedx.frontend.widget.footer.desktopLayout.v1), following the header's convention. The existing JSX from Footer.tsx (the <footer> element, the column composition, <PoweredBy>) moves verbatim into a new DesktopFooterLayout component registered as the default widget for that slot. Footer.tsx then collapses to a single <Slot>, symmetric with Header.tsx.

This is purely additive. Every existing slot id, every default widget id, and the column composition stay verbatim. Plugins targeting the existing column slots keep working unchanged. The DOM tree for default deployments is identical aside from one extra <Slot> rendering pass. The net new public surface is one slot id and one widget id, both following the reverse-DNS-versioned convention from ADR 0009.

After the change, replacing the whole footer becomes a single WidgetOperationTypes.REPLACE (or LayoutOperationTypes.REPLACE) targeting the new id, mirroring the header's desktopLayout.v1 / mobileLayout.v1 pattern. A short ADR documenting the symmetry ("layout widgets host shell composition; top-level slots are layout-replacement seams") and a docs page demonstrating both replacement paths for header and footer side by side would round out the change.

Out of scope

Splitting desktop and mobile footer layouts (the current responsive single-layout design works; defer until a real reason emerges). Wrapping <PoweredBy> in its own slot so operators can remove just that piece without replacing the whole layout (reasonable follow-up, but widens scope and is not blocking). Generic "layout replacement" abstractions beyond what LayoutOperationTypes.REPLACE already provides.

Metadata

Metadata

Assignees

Projects

Status

Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions