bevy-ui
Skill29 starsChanged 11 days agoMITView on GitHub
Use when building Bevy 0.20 UI with `Node`, `ui_widgets::Button`, `Hovered` / `Pressed` / `Activate`, `children![]`, `TextFont` with `FontSource` and `FontSize`, `BorderRadius` / `CornerRadius`, `BackgroundColor`, `BorderColor`, `InputFocus`, or `AccessibleLabel`, or when `ui::Interaction` and `Changed<Interaction>` show deprecation warnings.
In this repository34 other files it gives its agents.
- AGENTS.mdAGENTS.md
- CLAUDE.mdCLAUDE.md
- .github/copilot-instructions.mdCopilot instructions
- bevy-a11ySkill
- bevy-animationSkill
- bevy-assetsSkill
- bevy-audioSkill
- bevy-camerasSkill
- bevy-captureSkill
- bevy-cargo-featuresSkill
- bevy-core-conceptsSkill
- bevy-custom-assetsSkill
- bevy-diagnostics-profilingSkill
- bevy-ecs-componentsSkill
- bevy-ecs-queriesSkill
- bevy-ecs-systemsSkill
- bevy-fluentSkill
- bevy-input-actionsSkill
- bevy-migration-0-17-to-0-18Skill
- bevy-migration-0-18-to-0-19Skill
- bevy-migration-0-19-to-0-20Skill
- bevy-pbr-materialsSkill
- bevy-physicsSkill
- bevy-portingSkill
- bevy-renderingSkill
- bevy-save-loadSkill
- bevySkill
- bevy-testingSkill
- bevy-vfxSkill
- bevy-voxel-dataSkill
- bevy-voxel-pipelineSkill
- bevy-voxel-runtimeSkill
- bevy-wasm-webgpuSkill
- similarity-rsSkill
The file
We split this skill into the rules it gives an agent
1 rule. About 0 are common, 0 are distinctive, and 1 is very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈0
Distinctive · estimate
This author's own choices. Worth a look.
1
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 149
InputFocusis inserted byDefaultPlugins(0.19+): Do not init_resource it. Fields are private: get(), set(entity, FocusCause::Pressed), …
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Bevy 0.20 — UI
When to use this skill
- Spawning any
Node-based UI element (panels, buttons, text labels, overlays). - Reacting to a button:
Hovered,Has<Pressed>, and theActivateevent. - Styling text with
TextFont { font: FontSource, font_size: FontSize, .. },TextColor,TextShadow. - Setting
BackgroundColor,BorderColor, orNode::border_radius(BorderRadiusofCornerRadius). - Using palette constants from
bevy::color::palettes. - Wiring
InputFocus,TabIndex, orAccessibleLabelfor keyboard and screen-reader support. - Nesting child entities with
children![]or.with_children(...). - Migrating code that used
ui::Interaction,Changed<Interaction>,font_size: 33.0, orfont: handle(0.18 forms).
Canonical pattern
Headless ui_widgets::Button, styled by polling Hovered and Pressed, clicked via Activate. Mirrors examples/ui/widgets/button.rs in Bevy 0.20.1.
use bevy::{
color::palettes::basic::*,
picking::hover::Hovered,
prelude::*,
ui::Pressed,
ui_widgets::{observe, Activate, Button},
};
fn main() {
App::new()
// DefaultPlugins already includes UiWidgetsPlugins and InputFocusPlugin.
.add_plugins(DefaultPlugins)
.add_systems(Startup, setup)
.add_systems(Update, update_button_appearance)
.run();
}
const NORMAL_BUTTON: Color = Color::srgb(0.15, 0.15, 0.15);
const HOVERED_BUTTON: Color = Color::srgb(0.25, 0.25, 0.25);
const PRESSED_BUTTON: Color = Color::srgb(0.35, 0.75, 0.35);
fn setup(mut commands: Commands, assets: Res<AssetServer>) {
commands.spawn(Camera2d);
commands.spawn((
// Full-screen flex container that centres the button.
Node {
width: percent(100),
height: percent(100),
align_items: AlignItems::Center,
justify_content: JustifyContent::Center,
..default()
},
children![(
button(&assets),
// `Button` fires `Activate` on a completed click (or Enter/Space when focused).
observe(|_activate: On<Activate>| {
info!("Button clicked!");
}),
)],
));
}
fn button(asset_server: &AssetServer) -> impl Bundle {
(
Button, // headless widget: tracks `Pressed`, emits `Activate`
Hovered::default(), // picking backend keeps this up to date
Node {
width: px(150),
height: px(65),
border: UiRect::all(px(5)),
justify_content: JustifyContent::Center,
align_items: AlignItems::Center,
border_radius: BorderRadius::MAX, // pill
..default()
},
BorderColor::all(Color::BLACK),
BackgroundColor(NORMAL_BUTTON),
children![(
Text::new("Button"),
TextFont {
font: asset_server.load("fonts/FiraSans-Bold.ttf").into(), // Handle -> FontSource
font_size: FontSize::Px(33.0),
..default()
},
TextColor(Color::srgb(0.9, 0.9, 0.9)),
TextShadow::default(),
)],
)
}
/// Poll `Hovered` + `Has<Pressed>` every frame and pick a look.
fn update_button_appearance(
mut buttons: Query<
(&Hovered, Has<Pressed>, &mut BackgroundColor, &mut BorderColor, &Children),
With<Button>,
>,
mut text_query: Query<&mut Text>,
) {
for (hovered, pressed, mut color, mut border_color, children) in &mut buttons {
let Ok(mut text) = text_query.get_mut(children[0]) else { continue };
match (hovered.get(), pressed) {
(_, true) => {
**text = "Press".to_string();
*color = PRESSED_BUTTON.into();
border_color.set_all(RED);
}
(true, false) => {
**text = "Hover".to_string();
*color = HOVERED_BUTTON.into();
border_color.set_all(WHITE);
}
(false, false) => {
**text = "Button".to_string();
*color = NORMAL_BUTTON.into();
border_color.set_all(BLACK);
}
}
}
}Topics
| Topic | Reference |
Node, Val (px, percent, em, rem, vw, vh), flex recipes | references/layout.md |
Text::new, TextFont, FontSource, FontSize, TextLayout::justify | references/text.md |
ui_widgets::Button, Hovered, Pressed, Activate, observers vs polling | references/interaction.md |
BackgroundColor, BorderColor, BorderRadius / CornerRadius | references/colors-and-borders.md |
bevy::color::palettes::{basic,css,tailwind} | references/palettes.md |
InputFocus, FocusCause, TabIndex, AccessibleLabel | references/accessibility.md |
children![] vs .with_children(...) | references/children-macro.md |
| Cross-cutting traps and stale-LLM output | references/gotchas.md |
Gotchas
ui::Interactionandui::widget::Buttonare deprecated in 0.20. Both still sit inbevy::prelude, soButtonfrom the prelude is the old one. Importbevy::ui_widgets::Buttonexplicitly. ReplaceChanged<Interaction>matches with&Hovered+Has<Pressed>(both are not in the prelude:bevy::picking::hover::Hovered,bevy::ui::Pressed).- Clicks are an event, not a state.
Activatefires once per completed click;Pressedis only present while the pointer is held. Attachobserve(|_: On<Activate>| ..)in the bundle orcommands.entity(e).observe(..). button.set_changed()is gone with the old button.ui_widgets::ButtonrequiresAccessibilityNode(Role::Button); addAccessibleLabel("...")for the spoken name. No manual dirty-marking.InputFocusis inserted byDefaultPlugins(0.19+). Do notinit_resourceit. Fields are private:get(),set(entity, FocusCause::Pressed),clear().FocusCausegainedAutoin 0.20; exhaustive matches must add it.TextFontfields changed in 0.19.font: Handle<Font>becameFontSource(handle.into(),FontSource::family("Fira Sans"),FontSource::sans_serif());font_size: f32becameFontSize::Px(f32).TextFont::default()isFontSize::Rem(1.)in 0.20 (20 px viaRemSize), so setFontSize::Px(20.)for a fixed size.FontSource::SansSerif(0.19) isFontSource::sans_serif()in 0.20.BorderRadiusfields areCornerRadiusin 0.20.BorderRadius::MAXandBorderRadius::px(..)are unchanged;BorderRadius::all(px(6))works viaInto<CornerRadius>; literal structs needCornerRadius::circular(px(10.)).Val::Em/Val::Remare new in 0.20.Val::resolvetakesEmSizeandRemSizeas two extra arguments.- Flat pointer events in 0.20.
On<Pointer<Click>>isOn<PointerClick>;press.pointer_location.positionispress.pointer.position. uiis not implied by2d/3dsince 0.19. Withdefault-features = false, add theuifeature collection (it also carriesbevy_ui_widgetsandui_picking).TextLayout::new_with_justifyisTextLayout::justify(0.19).Justifyis in the prelude.
See also
bevy-migration-0-19-to-0-20— full list of the button, pointer-event, andValchanges.bevy-cameras— spawn aCamera2dalongside any UI scene.bevy-ecs-queries—Has<T>,Changed<T>, and query filters used above.bevy-fluent— localized UI text viaFluentText<T>.bevy-cargo-features— theuicollection and what it enables.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

