Skip to content

Implement Feathers toasts#24071

Draft
villejuutila wants to merge 8 commits intobevyengine:mainfrom
villejuutila:feathers-toasts
Draft

Implement Feathers toasts#24071
villejuutila wants to merge 8 commits intobevyengine:mainfrom
villejuutila:feathers-toasts

Conversation

@villejuutila
Copy link
Copy Markdown

@villejuutila villejuutila commented May 2, 2026

Objective

This PR adds one type of implementation of toasts/notifications in Feathers widgets.

  • Toasts can have different severity levels with FeathersToastProps::variant, currently there are Info, Success, Warning and Error. Defaults to ToastVariant::Info.

  • Toasts can be positioned with FeathersToastProps::position. Possible values are TopLeft, TopRight, BottomRight and BottomLeft. Defaults to ToastPosition::BottomLeft.

  • Toasts can be set to despawn automatically with FeathersToastProps::duration. Defaults to 3 seconds.

    • Or they can be indefinite with by passing None. That way they're only closed by pressing the close icon.
  • Toasts are positioned with position_type: PositionType::Absolute and their positioning is handled in ToastPosition::on_add and ToastPosition::on_despawn Component hooks.

  • ToastPositions resource keeps track of each currently spawned toast as HashMap<ToastPosition, Vec<Entity>>. When a toast is despawned, the despawned toasts on_despawn hook re-positions the remaining (higher indexed) toasts with same ToastPosition

    • I initially implemented this in a different way where a ToastContainer component was spawned lazily for each corner and the toasts themselves were children of that Node. That didn't require absolute positioning as Transform propagation handled (re)positioning of the toasts. However discussions in Discord ui channel led me to try this type of approach instead as I thought that goal would be more easily achieved with this kind of architecture. I don't have a strong opinion in either approach and I'm open to suggestions!

    I have some thoughts on a general framework for animated ui transitions, which would make the toasts even snazzier. Part of this work was added last release: fallible interpolation, which lets you animate Val types.

  • Part of Bevy Standard Widgets: Roadmap #19236 even tho notifications are under Core widgets in the tracking issue these are implemented as only Feathers widgets per discussions with @viridia.

    My inclination would be to have toasts in feathers but not in the standard widgets - it seems like the kind of thing that would get used in an editor, but not so much in games generally. I suspect that most games would want to display transitory feedback in a way that fits more closely with the game's art style and theme.

  • Please consider the visual aspects of the toasts as a mere example, I'm terrible at designing user interfaces and I'm expecting some more professional designers to help me iterate this towards the final form, should this feature be considered wanted!

TODO:

  • Add support for asynchronous(?) toasts for e.g. loading cases where the toasts would be visible until a given asynchronous operation is completed
    • Despawning should be fairly simple either a) no library code needed, consumers just store the Entity of the toast and despawn it once complete or b) observe an EntityEvent on the toast that is triggered by the consumer once the asynchronous operation is complete
    • That should use some form of loading indicator, should it be a) a circular progress indicator (loader-circle from lucide or b) a loading bar?
  • Add support for multiline messages. Currently the message can only take 90% of the toasts width and all the overflow is clipped
  • Change the newly added colors to be what Bevy UI designers want to use for these severity levels.

Testing

  • Reviewers can test the changes in the feathers_gallery example with cargo run --example feathers_gallery --features experimental_bevy_feathers

Showcase

Screen.Recording.2026-05-02.at.9.20.09.mov

Spawning a toast is as simple as

bsn! {
    :FeathersToast {
        @message: "Default success toast in bottom right",
    }
};

- Added four new (definitely not final) palette colors for severity levels error, warning, success and info
- Implemented a toast widget that shows a progress bar and despawns automatically when duration reaches zero
- Toast::on_remove component hook repositions other toasts higher in order than the removed toast
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 2, 2026

Welcome, new contributor!

Please make sure you've read our contributing guide, as well as our policy regarding AI usage, and we look forward to reviewing your pull request shortly ✨

@viridia
Copy link
Copy Markdown
Contributor

viridia commented May 2, 2026

I'd like to point your attention to the visual style seen in react-toastify: https://www.npmjs.com/package/react-toastify - if you follow that link, you will see a video that shows examples of three different styles (color schemes) of toasts. Some of the styles are bright and colorful, others are more subdued. I think that for the Bevy editor, we probably want one of the more subdued styles - I think that bright, fluorescent colors will clash with the overall editor aesthetic.

Ideally we'd get some of our artist volunteers to work on this, however the vibe I am getting from them is that they are not in favor of toasts as a feedback mechanism and prefer to use other means for status notifications (like messages in the status bar). That's OK, because I think toasts could be valuable even if the official bevy editor doesn't use them, as other editors using feathers might want to use toasts.

@tacuna
Copy link
Copy Markdown

tacuna commented May 2, 2026

Part of #19236 even tho notifications are under Core widgets in the tracking issue these are implemented as only Feathers widgets per discussions with @viridia.

My inclination would be to have toasts in feathers but not in the standard widgets - it seems like the kind of thing that would get used in an editor, but not so much in games generally. I suspect that most games would want to display transitory feedback in a way that fits more closely with the game's art style and theme.

While i can understand the rationale, I'm a bit surprised as this would break the mental model of "feathers is only opinionated theming on top of core widgets" (or maybe my understanding of feather's purpose is wrong). Regardless where it sits, games can always opt to not use/create their own widgets for something; why (try to) make that decision for them in advance?

@viridia
Copy link
Copy Markdown
Contributor

viridia commented May 2, 2026

Ah, this same issue came up in the discussion on #24060 and I'll quote my response here:

First off, we already have several widgets within feathers that have no headless underpinning. Not counting the passive widgets like flex_spacer or label, we have one interactive control in this category: color swatch.

In the react / web world, headless ui libraries generally have a small number of widgets, usually about 12 to 20. The opinionated libraries built on top of these have a much larger and more diverse collection of widgets, often on the order of 50 unique types. There's a couple of reasons for this: sometimes it's because multiple different opinionated widgets map to the same headless widget. For example, "swatch grid" (a widget that shows a palette of colors, one of which can be selected) is really just a radio button group. That is, the individual entries in the palette behave like radio buttons, they just happen to display a color swatch instead of a button image.

In other cases, it's because some opinionated widgets have no interactive behavior. Examples of this are "card" or "avatar" widgets.

Whether a widget has a headless impl or is feathers-only depends, I think, on the following criteria:

  • Is the widget interactive or passive?
  • If you remove the parts of the widget responsible for styling and animation, is there much left?
  • Is the widget likely to be used in both games and editors?
  • Does the widget have non-trivial accessibility attributes and behavior?

Copy link
Copy Markdown
Contributor

@amtep amtep left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi I am new to bevy but I did the best I could :) I may be mistaken about how some things work though. I do have several years of experience with Rust.

let mut entity_mut = world.entity_mut(entity);
let mut node = entity_mut
.get_mut::<Node>()
.expect("Node should be present in ToastPosition on_add");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this guaranteed? I've had issues with on_add firing on a component before other components have been added to an entity.

Also, ToolPosition is pub and doesn't have require(Node) so you're relying on the user to make sure there is a Node.

Panicking may be extreme in this situation. Can you insert the Node if it's missing, instead?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be honest I have no idea if it's guaranteed but at least I've never personally experienced it not being there.
The only intended way to spawn a Toast is via the FeathersToast SceneComponent and it's fn scene adds the Node.. E.g. as described in the description

bsn! {
    :FeathersToast {
        @message: "Default success toast in bottom right",
    }
};

Comment thread crates/bevy_feathers/src/feedback/toast.rs Outdated
match position {
ToastPosition::BottomRight => {
node.bottom = px(offset);
node.right = px(10.0);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style note

This 10.0 here, is it TOAST_MARGIN as well? Should probably get a named constant.

More generally, I feel it would be better to get these layout sizes from a ToastSettings resource that the user can insert.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good suggestion to replace the literal value with TOAST_MARGIN_PX, that's done now.

As for user configurable settings I think the Feathers implementation shouldn't have user configurable settings as they're supposed to (AFAIK) be very opinionated. If it's decided that there should be bevy_ui_widgets headless implementation of toasts, then definitely all of the styling and behavioural settings should be user configurable (see #24071 (comment)).

#[reflect(Component, Clone, Default)]
#[component(on_add, on_despawn)]
pub enum ToastPosition {
/// Bottom right corner of the screen. Siblings stack upwards.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This says "of the screen", but if a ToastPosition has a parent entity, does it get laid out inside the parent? (Which would be a useful feature, if it isn't already)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The intended usage of these is only via the FeathersToast SceneComponent and therefore this implementation hasn't even considered such usage.

#[derive(Component, Clone, Default, Reflect, Debug, PartialEq, Eq, Hash)]
#[reflect(Component, Clone, Default)]
#[component(on_add, on_despawn)]
pub enum ToastPosition {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel TopCenter and BottomCenter would also be useful. My main experience with toasts (Paradox's Crusader Kings game) has them top center.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Imo and afaik the Feathers implementation should focus on editor/tooling UI. Should there be a need for Bevy provided toasts in games, that should reside in bevy_ui_widgets as a headless widget. I'll leave this open and not make any changes should we get more opinions on this. I'm more than okay with moving these to bevy_ui_widgets and provide a Feathers reference implementation tho!

Comment thread crates/bevy_feathers/src/feedback/toast.rs Outdated
Self {
message: "".to_string(), // TODO: Could multiline messages be supported by passing a [`SceneList`]?
variant: ToastVariant::default(),
duration: Some(Duration::from_secs(3)),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Opinion

I feel that None would be a better default than picking a number of seconds, which is by nature arbitrary.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have a strong opinion in either direction. I'll keep this open an unchanged should more people want to weigh their opinions.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

However the duration of 3 sec came from Androids Snackbar.LENGTH_SHORT

Comment thread crates/bevy_feathers/src/feedback/toast.rs Outdated
Comment thread crates/bevy_feathers/src/feedback/toast.rs Outdated
- Renamed TOAST_HEIGHT to TOAST_HEIGHT_PX and TOAST_MARGIN to TOAST_MARGIN_PX
- Replaced 10.0 literals with TOAST_MARGIN_PX
- Corrected negative offset in ToastPosition::on_despawn
- Replaced unwrapping props.duration with `if let`
- Unconditionally always spawn Toast close button
@alice-i-cecile alice-i-cecile added C-Feature A new feature, making something new possible A-UI Graphical user interfaces, styles, layouts, and widgets labels May 3, 2026
@github-project-automation github-project-automation Bot moved this to Needs SME Triage in UI May 3, 2026
@alice-i-cecile alice-i-cecile added S-Needs-Review Needs reviewer attention (from anyone!) to move forward M-Release-Note Work that should be called out in the blog due to impact D-Modest A "normal" level of difficulty; suitable for simple features or challenging fixes labels May 3, 2026
@alice-i-cecile alice-i-cecile added this to the 0.20 milestone May 3, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 3, 2026

It looks like your PR has been selected for a highlight in the next release blog post, but you didn't provide a release note.

Please review the instructions for writing release notes, then expand or revise the content in the release notes directory to showcase your changes.

@cart cart closed this May 5, 2026
@github-project-automation github-project-automation Bot moved this from Needs SME Triage to Done in UI May 5, 2026
@cart cart reopened this May 5, 2026
@github-project-automation github-project-automation Bot moved this from Done to Needs SME Triage in UI May 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-UI Graphical user interfaces, styles, layouts, and widgets C-Feature A new feature, making something new possible D-Modest A "normal" level of difficulty; suitable for simple features or challenging fixes M-Release-Note Work that should be called out in the blog due to impact S-Needs-Review Needs reviewer attention (from anyone!) to move forward

Projects

Status: Needs SME Triage

Development

Successfully merging this pull request may close these issues.

6 participants