Skip to content

Migrate tab-panel height/overflow CSS off MudBlazor's panel wrapper (9.6.0 display:contents contract) #1039

Description

@codemonkey85

Background

MudBlazor 9.6.0 (MudBlazor#13288) made the tab panel wrapper <div role="tabpanel"> layout-transparent: inactive panels are display: none, the active panel is display: contents — it no longer generates a layout box. Every height/overflow/display rule app.css applied to .mud-tab-panel was silently ignored, which broke all scrolling on narrow screens (reported in #1030 as "the edit window isn't appearing"; the edit form was clipped inside an unscrollable panel).

PR #1037 (commit 7164068) shipped a compatibility shim that pins the active panel back to the 9.5.x block box:

.mud-tabs-panels > .mud-tab-panel.mud-tab-panel-active:not(.mud-tab-panel-hidden) {
    display: block;
}

This works (verified end-to-end), but it fights MudBlazor's new contract — don't style the panel wrapper; style your own content element or the panels container — and it depends on MudBlazor's internal class names plus winning a (0,4,0) specificity tie by file order. The next MudBlazor bump can break it again the same way 9.6.0 did. This issue tracks the proper migration so the shim can be deleted.

Current wrapper-dependent rules (the migration inventory)

All in Pkmds.Rcl/wwwroot/css/app.css:

Line (approx) Breakpoint Rule Purpose
~71 global .mud-tabs-panels > .mud-tab-panel.mud-tab-panel-active:not(...)display: block The shim — delete when done
~363 ≥1280px .save-file-outer-tabs > .mud-tabs-panels > .mud-tab-panelheight: 100%; overflow: hidden Desktop: panel clips, per-tab wrappers scroll
~392 ≥1280px … > .mud-tab-panel:has(.bank-tab-content)display: flex; flex-direction: column Bank tab flex cascade
~558 <1280px … > .mud-tabs-panels > .mud-tab-panelheight: 100%; overflow-y: auto; overflow-x: hidden Phone/tablet: the panel IS the page's only scroll container
~568 <1280px … > .mud-tab-panel:has(.pokedex-tab-content)overflow: hidden; display: flex; flex-direction: column Pokédex owns its own scroll (Virtualize sizing)

Also: the comment near line ~1109 references the panel's overflow-x: hidden — update it when the rule moves.

Tab wrapper inventory (SaveFileComponent.razor)

Tabs that already have a dedicated content-wrapper class: Party/Box (party-box-tab-content), Trainer (trainer-tab-content), Feebas (feebas-tab-content), Teams (teams-tab-content), Bank (bank-tab-content), Trade (trade-tab-content), Pokédex (pokedex-tab-content, inside PokedexTab).

Tabs with no wrapper class (just div.mt-3 or the bare component): Bag, Mystery Gifts, Wonder Cards, Records, RTC, Legality Report, Search, Encounter DB, Batch Editor.

Implementation plan

Strategy: make .mud-tabs-panels the layout/scroll container (recommended — under display: contents the tab content already behaves as a direct child of it, and since KeepPanelsAlive is not used, only the active tab's content is ever in the DOM).

  1. Give the panels container a definite height. MudBlazor's horizontal .mud-tabs root is not a flex container, so add at both breakpoints:

    .save-file-outer-tabs { display: flex; flex-direction: column; }
    .save-file-outer-tabs > .mud-tabs-panels { flex: 1; min-height: 0; }

    (.save-file-outer-tabs already gets flex: 1; min-height: 0; overflow: hidden from its own parent cascade — keep that.) Verify the tab header bar (.mud-tabs-tabbar) keeps its natural height as the non-growing flex child.

  2. Narrow screens (<1280px): move the default scroll role from the panel to the container. Replace the > .mud-tab-panel { height:100%; overflow-y:auto; overflow-x:hidden } rule with:

    .save-file-outer-tabs > .mud-tabs-panels { overflow-y: auto; overflow-x: hidden; }

    Tabs without wrapper classes then need no CSS at all — their content scrolls in the container. This removes the need to invent wrapper classes for Bag/Mystery Gifts/Wonder Cards/Records/RTC/Legality/Search/Encounter DB/Batch Editor.

  3. Narrow screens: Pokédex opt-out moves up a level. The Virtualize component needs a definite, viewport-bounded height, so the container must not scroll when the Pokédex tab is active. The panel wrapper is still present in the DOM tree (it just has no box), so :has() still matches through it:

    .save-file-outer-tabs > .mud-tabs-panels:has(> .mud-tab-panel-active .pokedex-tab-content) {
        overflow: hidden; display: flex; flex-direction: column;
    }

    .pokedex-tab-content { height: 100%; overflow: hidden } (global, existing) keeps working because its containing block is now the flexed container. Confirm the virtualized species list still renders full-length (the failure mode is it cutting off at ~106 rows).

  4. Desktop (≥1280px): drop the panel clipping rule, keep clipping on the container. The container already has overflow: hidden at this breakpoint; delete the > .mud-tab-panel { height: 100%; overflow: hidden } rule. Per-tab wrappers (trade-, feebas-, teams-, trainer-tab-content, all height: 100%; overflow-y: auto) now resolve their percentage height against the flexed .mud-tabs-panels from step 1 — this is the part most likely to shift pixels, since their old containing block was the panel box. Verify each.

  5. Desktop: Bank flex cascade moves up a level, mirroring step 3:

    .save-file-outer-tabs > .mud-tabs-panels:has(> .mud-tab-panel-active .bank-tab-content) {
        display: flex; flex-direction: column;
    }

    .bank-tab-content { flex: 1; min-height: 0; … } (existing) then participates directly in the container's flex layout — with display: contents on the wrapper this actually gets simpler than today.

  6. Inner edit-form tabs need no migration.edit-form-tabs .mud-tabs-panels rules already style the container, not the panel wrapper. Sanity-check only.

  7. Delete the shim (the display: block override near the top of app.css) and its comment, plus the two now-dead :has() panel rules. Update the stale comment near line ~1109.

  8. Watch for specificity ties: MudBlazor's own .mud-tabs-panels.mud-tabs-vertical { display: flex } and the transition on .mud-tabs-panels should be unaffected, but diff computed styles before/after on the container to be sure.

Verification matrix

Playwright against a local Debug build (the method used to verify PR #1037: load TestFiles/Test-Save-White-2.sav, drive tabs via JS clicks, assert scrollHeight/scrollTop movement and element visibility):

  • Breakpoints: 393×750 (phone), 820×1180 (tablet, hits the 768–1279 edit-form rules), 1440×900 (desktop).
  • Per tab: bottom of content reachable by scroll; no double scrollbars; no horizontal scroll on phone.
  • Critical flows: select a box Pokémon on phone → edit form scrolls into view (the [Feedback] Please remove auto-rotate or at least an option to disable it. Thanks. #1030 regression); Pokédex species list renders full-length on phone and desktop; Bank card grid fills remaining height on desktop; Search/Encounter DB action buttons pinned visible at the bottom; Trainer tab scrolls on phone and short desktop windows.
  • Real device: one pass on an actual iPhone (Safari + installed PWA) since the original report was iOS.

Acceptance criteria

  • No app.css rule targets .mud-tab-panel inside .save-file-outer-tabs (wrapper is layout-transparent, per the MudBlazor 9.6 contract)
  • The display: block shim is deleted
  • Full verification matrix passes
  • Comments in app.css updated to describe the container-based cascade

Optional follow-up

Raise upstream on MudBlazor#13288 that the change silently breaks consumer CSS targeting the panel wrapper in a minor release, and that display: contents on an element with role="tabpanel" has a history of accessibility-tree bugs in some browsers.

🤖 Generated with Claude Code

https://claude.ai/code/session_01AVBABqa4sjEdMCnKvshS1t

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions