Add welcome dashboard widget with adaptive layout and content#78461
Conversation
use interactive-neutral-strong pair for dark surface + light text
import Icon from @wordpress/ui alongside Link/Stack/Text and infer its accepted prop type from the component itself.
wrap content in a Stack root with container-type: inline-size; let feature columns reflow via auto-fit minmax(220px, 1fr) and shrink banner padding + min-block-size below 480px.
return the committed layout instead of the original metadata value when one is already stored.
append welcome-banner (full width, order 0) alongside hello-world (order 1) and switch from early-return to conditional appends so both can coexist in the seed.
switch container-type to size so the banner can shrink (min-block-size and padding) when the tile is constrained vertically.
let widget content scroll vertically when it exceeds the tile height, in both regular and full-bleed presentations.
constrain the banner with flex-grow + min/max-height instead of a fixed min-block-size; tighten the narrow breakpoint to 420px and apply consistent paddings on the column block in both narrow and short modes.
drop the columns wrapper; banner and features now share a single auto-fit grid. Banner spans all columns by default and shrinks to span 2 once the widget is wide enough (1200px) so all tiles sit in one row.
revert the unified grid in favour of a column Stack root with a separate columns Stack. cap the banner with min/max-height so it stays balanced as the widget grows.
observe the root with useResizeObserver and switch the Stack direction to row above 900px, giving the banner 35% of the width and letting the features grid take the rest.
rename the width/height constants for clarity, drop the stale import comment, and trigger row layout on either a wide or short widget.
give the banner a minimum inline size and clear the container-query min-block-size so row mode stays consistent; drop dead commented rules.
move the banner markup and styles into components/banner and pass isWide down so its row-mode and container-query rules live with it. trim the orphaned banner selectors from the widget root styles.
let the columns track fill and vertically center its rows so the features stay balanced beside the banner in row layout.
below 120x120 the observer drops the feature columns and renders only the banner header + link, compacting its padding to fit the tile.
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message. To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
Co-authored-by: Mikael Korpela <mikael@ihminen.org>
Co-authored-by: Mikael Korpela <mikael@ihminen.org>
Co-authored-by: Mikael Korpela <mikael@ihminen.org>
Co-authored-by: Mikael Korpela <mikael@ihminen.org>
Co-authored-by: Mikael Korpela <mikael@ihminen.org>
| const className = [ | ||
| styles.banner, | ||
| isWide && styles.wide, | ||
| isTiny && styles.tiny, | ||
| ] | ||
| .filter( Boolean ) | ||
| .join( ' ' ); |
There was a problem hiding this comment.
Would clearly make sense to use clsx here, but can't because we don't have the package.json for each widget? 🤔
It might make sense to try to add it here as a test.
There was a problem hiding this comment.
Yes, makes sense to me. Or maybe creating a packages.json for all widget types
There was a problem hiding this comment.
I'm researching this issue, but I think it deserves a separate PR.
| direction={ isWide ? 'row' : 'column' } | ||
| gap="lg" | ||
| > | ||
| <Banner isWide={ isWide } isTiny={ isTiny } /> |
There was a problem hiding this comment.
Instead of CSS positioning, let's try how Card.FullBleed would work here now?
It's what I'd expect consumers to try and what we should make work easily (if it doesn't already).
We can't really do this Card rendering Stack trick:
gutenberg/packages/ui/src/card/stories/index.story.tsx
Lines 136 to 147 in 87e0a9b
...so applying FullBleed on whole thing and then adding padding back might be another way? 🤔
There was a problem hiding this comment.
Stepping back, the real thing to guarantee here is design coherence: the padding the widget puts around its inner content should match what the chrome (Card.Content) applies.
Card.FullBleed would get us there, but by coupling the widget render to the surface's chrome system.
To me, the way that keeps the two independent is orthogonal: both sides resolve the same DS token (--wpds-dimension-padding-*) rather than one reaching into the other. The token is the shared coordination point, not a direct dependency.
Today, that coherence is by convention: the widget happens to pick the same token Card.Content does.
The missing piece to make it robust is a shared default, a semantic token that both the Chrome and the widget layout resolve from, so the value stays in sync from a single source instead of being matched by hand.
simison
left a comment
There was a problem hiding this comment.
Looking good! Let's get it in.
I think it would still make sense to add in this PR (and fine in follow-up too) a conditional content based on block theme or not, just like the original widget does as well.
Example here that worked well.
Another thing is the "dismissal" button for the header, which can now be hooked into actually removing the banner from the dashboard.
(example here)
Welcome banner widget
|
Flaky tests detected in 3845d69. 🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/26177260217
|
returning the committed sub-array short-circuited get_user_metadata with the wrong shape and dropped the preference scope; return the original value so WordPress resolves the stored layout intact.
drop a redundant flex rule, a leading blank line, and an explanatory comment in the welcome-banner widget.
align dir and package name with the core/welcome id, update the seed instance UUID, and declare react in the per-widget package.json.
read is_block_theme from core-data; classic themes get Customizer and block-theme-discovery links instead of the site editor and styles.
Welcome banner widget
Done in the latest push. The content is now theme-aware: it reads |
This is a major one, similar to the Having a dismiss button in the widget content (render) sounds reasonable to me. Working on it on a follow-up. |

What?
Adds a
core/welcomedashboard widget that greets the user and surfaces a few entry points, adapting to both its tile size and the active theme.auto-fit; the banner compacts via container queries on narrow/short tiles; it switches to a side-by-side row on wide-and-short tiles (useResizeObserver); and drops to header-only below 120×120.is_block_themeviagetCurrentTheme()from core-data. Block themes get the site editor + styles links; classic themes get the Customizer and a block-theme discovery link.presentationhint; the surface owns the chrome.package.json.Still a basic implementation: the version string is static for now, and a general mechanism for passing server-provided data to widgets is left as a follow-up.
Why?
The dashboard needs a welcoming entry point, and the widget framework needs a concrete example exercising resize, presentation, theme-aware content, and chrome scrolling end to end.
How?
widgets/welcome/—render.tsxcomposes aBannerandFeatureHighlightcards inside a size-containerStackroot, and readsgetCurrentTheme()?.is_block_themeto branch columns 2 and 3.interactive-neutral-strongtoken pair; layout adapts viaauto-fit, container queries, and an observer-driven row/tiny switch.widget-chrome.module.css—overflow-y: autoon the content.default-layout-seed.php— seeds the widget at full width.Testing
Follow-ups