feat(toast): redesign toast notifications with dark mode design system - #1484
Conversation
Redesign the toast notification UI to align with the data-manager style guide and improve visual design, accessibility, and consistency. Functional changes: - Extract levelIcon as a separate field so subsequent withContent calls cannot accidentally destroy the status icon (fix for missing icons). - Use official Vaadin icons (vaadin:check, vaadin:close, vaadin:info) rendered at 14×14px in white inside 28×28 colored circles, instead of the smaller Lumo iconset (no Lumo info icon exists). - Rewrite toast CSS to use design tokens from the style guide instead of hardcoded hex colors; add 14 new CSS variables (--color-primary, --color-error, --color-success, --color-shade-*, --toast-background-color, --toast-text-color, etc.) in variables.css. - Increase link color from #1676f3 to #66A8FF to meet WCAG AA contrast requirements against the dark navy background. - Fix routing/info toast link visibility by adding CSS overrides for RouterLink/Button elements rendered inside the toast. - Use flexbox-based routing container instead of CSS Grid, simplifying the DOM while preserving ellipsis truncation for long messages via child combinator selectors. - Remove max-width for the toast overlay to let content determine width. - Fix pending task titles/subtexts rendering literal HTML markup by using Html wrappers instead of Div for title/subtext in withProgressBar(). - Wrap HTML content in <span> instead of <div style='display:contents'> to eliminate trailing block-level whitespace in toast messages. - Reimplement pendingTaskToast to build progress layout directly instead of delegating to the text-toast path. - Add actionToast factory method for error toasts with a 'Try Again' button.
741069b to
f983a8f
Compare
KochTobi
left a comment
There was a problem hiding this comment.
Hi Sven the new toasts look fancy. Please enable multiline messages again, use css classes and prevent duplicating css variables.
|
|
||
| private Component content; | ||
|
|
||
| private Optional<Component> levelIcon = Optional.empty(); |
There was a problem hiding this comment.
Please avoid Optional as fields. Rather take a nullable Component in this case. With Optional as field or method parameter you always increase complexity as the Optional itself may be null. I know that you are assigning an empty Optional here, but the field itself can be set to null by accident.
Instead of using it in the field, you can provide a private method, that wraps the field in an Optional.ofNullable so you can access it as Optional in your code. This way the returned Optional can never be null.
Optional also can cause problems with serialization (as it is not Serializable). For this reason a nullable field is preferred as well.
| private Component content; | ||
|
|
||
| private Optional<Component> levelIcon = Optional.empty(); | ||
| private Optional<Component> actionButton = Optional.empty(); |
There was a problem hiding this comment.
Please do not use Optional as field.
|
|
||
| private Optional<Component> levelIcon = Optional.empty(); | ||
| private Optional<Component> actionButton = Optional.empty(); | ||
| private Optional<String> title = Optional.empty(); |
There was a problem hiding this comment.
Please do not use Optional as field.
| private Optional<Component> levelIcon = Optional.empty(); | ||
| private Optional<Component> actionButton = Optional.empty(); | ||
| private Optional<String> title = Optional.empty(); | ||
| private Optional<String> subtext = Optional.empty(); |
There was a problem hiding this comment.
Please do not use Optional as field.
| color: white !important; | ||
| width: 14px !important; | ||
| height: 14px !important; |
There was a problem hiding this comment.
There should be no need for !important. Please use !important sparingly, as it cannot be overwritten by CSS selector specificity.
https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Cascade/Specificity
This also overwrites the size you set with .toast-icon which might be an undesired side-effect.
| * <li>Title (from message.text) in 18px bold</li> | ||
| * <li>Indeterminate progress bar</li> | ||
| * <li>Optional subtext (from message.subtext) in 16px regular</li> |
There was a problem hiding this comment.
I think you should keep the information on how the key is integrated in the message properties. This was included as it was unclear to developers.
| String messageText = parseMessage(key, parameters, locale); | ||
| Component content = switch (type) { | ||
| case HTML -> new Html("<div style=\"display:contents\">%s</div>".formatted(messageText)); | ||
| case HTML -> new Html("<span>%s</span>".formatted(messageText)); |
There was a problem hiding this comment.
Same error as before. If you wrap in a span instead of an invisible (display content) div, you loose any formatting in the messageText for linebreaks. This makes multiline messages impossible
|
Toast have different text sizes. Screen.Recording.2026-07-24.at.08.24.29.mov |
|
KochTobi
left a comment
There was a problem hiding this comment.
LGTM. However still some getStyle().set(\width`, some-value)` exist. Please have a look if you can replace them with existing or speaking CSS classes. Regarding the icons, why not use the vaadin icon instead of manipulating the HTML
| var button = new Button(LumoIcon.CROSS.create()); | ||
| private Button createCloseButton() { | ||
| var icon = new Icon(); | ||
| icon.getElement().setAttribute("icon", "vaadin:close-small"); |
There was a problem hiding this comment.
why not have a vaadin icon here?
KochTobi
left a comment
There was a problem hiding this comment.
Approved 😄 9,5/10 for a 10/10 please fix the ambivalent comment
…tion-style-update
…tion-style-update


Summary
Redesign the toast notification UI to align with the data-manager style guide and improve visual design, accessibility, and consistency.
Bug fixes
levelIconas a separate field so subsequentwithContent()calls cannot accidentally destroy the previously set iconRouterLink/Buttonelements rendered inside the toastlumo:info-circledoes not exist — switch tovaadin:info(symbol-only)vaadin:*-circleicons have their own filled-circle path — switch to symbol-only variantsvaadin:check,vaadin:close,vaadin:infowithProgressBar()usedDiv(title)— replace with wrappedHtmlelement<div style='display:contents'>with inline<span>wrapperVisual design
variables.css: design tokens (--color-primary,--color-error,--color-success), dark-surface shade tokens, and toast-specific tokens (--toast-background-color,--toast-link-color, etc.)#1676f3to#66A8FFto meet WCAG AA contrast--lumo-font-size-*) instead of hardcoded pxLayout
overflow: hidden; action buttons and close button are never clipped)flex: 1 1 0so text shrinks and buttons stay at natural widthToast message catalog (
toast-notifications.properties)<key>.message.subtextdocumentation comment and sample entries for progress toastsmeasurement.registration.in-progress.message.subtext('This might take a few minutes')project.updated.error.retry.message.text: removed inline<a>link in favor of the routed-link toast flow (the routing toast now renders the link as a styled button)ComponentDemo (test route)
ComponentDemodemo page with a two-column layout showing all toast types side by side (basic success/info/error, pending-task progress, routing, action-button, error with routing) — makes it easier to visually verify every toast variant in one placeexperiment.created.success,task.failed.action,measurement.completed.success,measurement.completed.routing,measurement.failed.retryRefactoring
pendingTaskToast: build progress layout directly instead of delegating to text-toast pathactionToastfactory method for error toasts with aTry AgainbuttonFiles changed
toast.cssvariables.cssToast.javavaadin:\*icons,Htmlwrappers for progress title/subtextMessageSourceNotificationFactory.java<span>wrapper for HTML content, newactionToastfactorytoast-notifications.propertiesComponentDemo.java