diff --git a/Cargo.toml b/Cargo.toml
index 9602ef33fbb58..eb6cf2816dae4 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -778,6 +778,17 @@ description = "Renders an animated sprite"
 category = "2D Rendering"
 wasm = true
 
+[[example]]
+name = "sprite_sorting"
+path = "examples/2d/sprite_sorting.rs"
+doc-scrape-examples = true
+
+[package.metadata.example.sprite_sorting]
+name = "Sprite Sorting"
+description = "Demonstrates how to sort sprites"
+category = "2D Rendering"
+wasm = true
+
 [[example]]
 name = "sprite_tile"
 path = "examples/2d/sprite_tile.rs"
diff --git a/crates/bevy_core_pipeline/src/core_2d/mod.rs b/crates/bevy_core_pipeline/src/core_2d/mod.rs
index 725ac38ed9762..6058504c8c7b4 100644
--- a/crates/bevy_core_pipeline/src/core_2d/mod.rs
+++ b/crates/bevy_core_pipeline/src/core_2d/mod.rs
@@ -343,9 +343,25 @@ impl CachedRenderPipelinePhaseItem for AlphaMask2d {
     }
 }
 
+#[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Hash, Copy, Clone)]
+pub struct Transparent2dSortKey {
+    z_index: i32,
+    bias: FloatOrd,
+}
+
+impl Transparent2dSortKey {
+    pub fn new(z_index: i32, bias: Option<f32>) -> Transparent2dSortKey {
+        Transparent2dSortKey {
+            z_index,
+            // nans sort after any valid specified y sort
+            bias: FloatOrd(bias.unwrap_or(f32::NAN)),
+        }
+    }
+}
+
 /// Transparent 2D [`SortedPhaseItem`]s.
 pub struct Transparent2d {
-    pub sort_key: FloatOrd,
+    pub sort_key: Transparent2dSortKey,
     pub entity: (Entity, MainEntity),
     pub pipeline: CachedRenderPipelineId,
     pub draw_function: DrawFunctionId,
@@ -395,7 +411,7 @@ impl PhaseItem for Transparent2d {
 }
 
 impl SortedPhaseItem for Transparent2d {
-    type SortKey = FloatOrd;
+    type SortKey = Transparent2dSortKey;
 
     #[inline]
     fn sort_key(&self) -> Self::SortKey {
@@ -405,7 +421,7 @@ impl SortedPhaseItem for Transparent2d {
     #[inline]
     fn sort(items: &mut [Self]) {
         // radsort is a stable radix sort that performed better than `slice::sort_by_key` or `slice::sort_unstable_by_key`.
-        radsort::sort_by_key(items, |item| item.sort_key().0);
+        radsort::sort_by_key(items, |item| (item.sort_key.z_index, item.sort_key.bias.0));
     }
 
     fn indexed(&self) -> bool {
diff --git a/crates/bevy_gizmos/src/pipeline_2d.rs b/crates/bevy_gizmos/src/pipeline_2d.rs
index a97071249d1a4..90b7aad73b83a 100644
--- a/crates/bevy_gizmos/src/pipeline_2d.rs
+++ b/crates/bevy_gizmos/src/pipeline_2d.rs
@@ -6,7 +6,7 @@ use crate::{
 };
 use bevy_app::{App, Plugin};
 use bevy_asset::{load_embedded_asset, Handle};
-use bevy_core_pipeline::core_2d::{Transparent2d, CORE_2D_DEPTH_FORMAT};
+use bevy_core_pipeline::core_2d::{Transparent2d, Transparent2dSortKey, CORE_2D_DEPTH_FORMAT};
 
 use bevy_ecs::{
     prelude::Entity,
@@ -16,7 +16,6 @@ use bevy_ecs::{
     world::{FromWorld, World},
 };
 use bevy_image::BevyDefault as _;
-use bevy_math::FloatOrd;
 use bevy_render::sync_world::MainEntity;
 use bevy_render::{
     render_asset::{prepare_assets, RenderAssets},
@@ -343,7 +342,7 @@ fn queue_line_gizmos_2d(
                     entity: (entity, *main_entity),
                     draw_function,
                     pipeline,
-                    sort_key: FloatOrd(f32::INFINITY),
+                    sort_key: Transparent2dSortKey::new(i32::MAX, None),
                     batch_range: 0..1,
                     extra_index: PhaseItemExtraIndex::None,
                     extracted_index: usize::MAX,
@@ -365,7 +364,7 @@ fn queue_line_gizmos_2d(
                     entity: (entity, *main_entity),
                     draw_function: draw_function_strip,
                     pipeline,
-                    sort_key: FloatOrd(f32::INFINITY),
+                    sort_key: Transparent2dSortKey::new(i32::MAX, None),
                     batch_range: 0..1,
                     extra_index: PhaseItemExtraIndex::None,
                     extracted_index: usize::MAX,
@@ -425,7 +424,7 @@ fn queue_line_joint_gizmos_2d(
                 entity: (entity, *main_entity),
                 draw_function,
                 pipeline,
-                sort_key: FloatOrd(f32::INFINITY),
+                sort_key: Transparent2dSortKey::new(i32::MAX, None),
                 batch_range: 0..1,
                 extra_index: PhaseItemExtraIndex::None,
                 extracted_index: usize::MAX,
diff --git a/crates/bevy_sprite/src/mesh2d/material.rs b/crates/bevy_sprite/src/mesh2d/material.rs
index 3f76b516cdd3f..389c3a9c5a45d 100644
--- a/crates/bevy_sprite/src/mesh2d/material.rs
+++ b/crates/bevy_sprite/src/mesh2d/material.rs
@@ -5,6 +5,7 @@ use crate::{
 use bevy_app::{App, Plugin, PostUpdate};
 use bevy_asset::prelude::AssetChanged;
 use bevy_asset::{AsAssetId, Asset, AssetApp, AssetEventSystems, AssetId, AssetServer, Handle};
+use bevy_core_pipeline::core_2d::Transparent2dSortKey;
 use bevy_core_pipeline::{
     core_2d::{
         AlphaMask2d, AlphaMask2dBinKey, BatchSetKey2d, Opaque2d, Opaque2dBinKey, Transparent2d,
@@ -18,7 +19,6 @@ use bevy_ecs::{
     prelude::*,
     system::{lifetimeless::SRes, SystemParamItem},
 };
-use bevy_math::FloatOrd;
 use bevy_platform::collections::HashMap;
 use bevy_reflect::{prelude::ReflectDefault, Reflect};
 use bevy_render::camera::extract_cameras;
@@ -839,7 +839,6 @@ pub fn queue_material2d_meshes<M: Material2d>(
             };
 
             mesh_instance.material_bind_group_id = material_2d.get_bind_group_id();
-            let mesh_z = mesh_instance.transforms.world_from_local.translation.z;
 
             // We don't support multidraw yet for 2D meshes, so we use this
             // custom logic to generate the `BinnedRenderPhaseType` instead of
@@ -852,8 +851,11 @@ pub fn queue_material2d_meshes<M: Material2d>(
                 BinnedRenderPhaseType::UnbatchableMesh
             };
 
-            match material_2d.properties.alpha_mode {
-                AlphaMode2d::Opaque => {
+            let needs_sort = mesh_instance.z_index.is_some()
+                || mesh_instance.y_sort
+                || mesh_instance.sort_bias.is_some();
+            match (material_2d.properties.alpha_mode, needs_sort) {
+                (AlphaMode2d::Opaque, false) => {
                     let bin_key = Opaque2dBinKey {
                         pipeline: pipeline_id,
                         draw_function: material_2d.properties.draw_function_id,
@@ -871,7 +873,7 @@ pub fn queue_material2d_meshes<M: Material2d>(
                         current_change_tick,
                     );
                 }
-                AlphaMode2d::Mask(_) => {
+                (AlphaMode2d::Mask(_), false) => {
                     let bin_key = AlphaMask2dBinKey {
                         pipeline: pipeline_id,
                         draw_function: material_2d.properties.draw_function_id,
@@ -889,7 +891,21 @@ pub fn queue_material2d_meshes<M: Material2d>(
                         current_change_tick,
                     );
                 }
-                AlphaMode2d::Blend => {
+                (AlphaMode2d::Blend, false) | (_, true) => {
+                    let mesh_y = mesh_instance.transforms.world_from_local.translation.y;
+                    let mesh_z = mesh_instance.transforms.world_from_local.translation.z;
+                    let z_bias = material_2d.properties.depth_bias;
+                    let sort_bias = mesh_instance.sort_bias;
+                    let bias = if mesh_instance.y_sort {
+                        mesh_y + z_bias + sort_bias.unwrap_or_default()
+                    } else {
+                        mesh_z + z_bias + sort_bias.unwrap_or_default()
+                    };
+
+                    let sort_key = Transparent2dSortKey::new(
+                        mesh_instance.z_index.unwrap_or_default(),
+                        Some(bias),
+                    );
                     transparent_phase.add(Transparent2d {
                         entity: (*render_entity, *visible_entity),
                         draw_function: material_2d.properties.draw_function_id,
@@ -898,7 +914,7 @@ pub fn queue_material2d_meshes<M: Material2d>(
                         // lowest sort key and getting closer should increase. As we have
                         // -z in front of the camera, the largest distance is -far with values increasing toward the
                         // camera. As such we can just use mesh_z as the distance
-                        sort_key: FloatOrd(mesh_z + material_2d.properties.depth_bias),
+                        sort_key,
                         // Batching is done in batch_and_prepare_render_phase
                         batch_range: 0..1,
                         extra_index: PhaseItemExtraIndex::None,
diff --git a/crates/bevy_sprite/src/mesh2d/mesh.rs b/crates/bevy_sprite/src/mesh2d/mesh.rs
index a3d9ee3eb23fd..a04f0f388c06e 100644
--- a/crates/bevy_sprite/src/mesh2d/mesh.rs
+++ b/crates/bevy_sprite/src/mesh2d/mesh.rs
@@ -137,6 +137,26 @@ impl Plugin for Mesh2dRenderPlugin {
     }
 }
 
+/// Describes the layer in which the sprite or mesh should be rendered. Higher values are rendered
+/// on top of lower values, with a default value of 0. This is useful for controlling the rendering
+/// order of sprites and always takes precedence over [`YSort`] or [`SortBias`].
+#[derive(Component, Deref, DerefMut, Default, Debug, Clone)]
+pub struct ZIndex(pub i32);
+
+/// A marker component that enables Y-sorting (depth sorting) for sprites and meshes.
+///
+/// When attached to an entity, this component indicates that the entity should be rendered
+/// in draw order based on its Y position. Entities with higher Y values (higher on screen)
+/// are drawn first, creating a depth illusion where objects lower on the screen appear
+/// in front of objects higher on the screen.
+#[derive(Component, Default, Debug, Clone)]
+pub struct YSort;
+
+/// An arbitrary bias value that can be applied to the sorting order of sprites and meshes and is
+/// applied after the [`ZIndex`] or added to the Y position of the entity if [`YSort`] is enabled.
+#[derive(Component, Deref, DerefMut, Default, Debug, Clone)]
+pub struct SortBias(pub f32);
+
 #[derive(Resource, Deref, DerefMut, Default, Debug, Clone)]
 pub struct ViewKeyCache(MainEntityHashMap<Mesh2dPipelineKey>);
 
@@ -228,6 +248,9 @@ pub struct RenderMesh2dInstance {
     pub material_bind_group_id: Material2dBindGroupId,
     pub automatic_batching: bool,
     pub tag: u32,
+    pub z_index: Option<i32>,
+    pub y_sort: bool,
+    pub sort_bias: Option<f32>,
 }
 
 #[derive(Default, Resource, Deref, DerefMut)]
@@ -245,13 +268,27 @@ pub fn extract_mesh2d(
             &GlobalTransform,
             &Mesh2d,
             Option<&MeshTag>,
+            Option<&ZIndex>,
+            Has<YSort>,
+            Option<&SortBias>,
             Has<NoAutomaticBatching>,
         )>,
     >,
 ) {
     render_mesh_instances.clear();
 
-    for (entity, view_visibility, transform, handle, tag, no_automatic_batching) in &query {
+    for (
+        entity,
+        view_visibility,
+        transform,
+        handle,
+        tag,
+        z_index,
+        y_sort,
+        sort_bias,
+        no_automatic_batching,
+    ) in &query
+    {
         if !view_visibility.get() {
             continue;
         }
@@ -266,6 +303,9 @@ pub fn extract_mesh2d(
                 material_bind_group_id: Material2dBindGroupId::default(),
                 automatic_batching: !no_automatic_batching,
                 tag: tag.map_or(0, |i| **i),
+                z_index: z_index.cloned().map(|x| x.0),
+                y_sort,
+                sort_bias: sort_bias.cloned().map(|sb| sb.0),
             },
         );
     }
diff --git a/crates/bevy_sprite/src/render/mod.rs b/crates/bevy_sprite/src/render/mod.rs
index 7602addc0b793..9c90dbd3fdcde 100644
--- a/crates/bevy_sprite/src/render/mod.rs
+++ b/crates/bevy_sprite/src/render/mod.rs
@@ -1,8 +1,8 @@
-use core::ops::Range;
-
-use crate::{Anchor, ComputedTextureSlices, ScalingMode, Sprite};
+use crate::{Anchor, ComputedTextureSlices, ScalingMode, SortBias, Sprite, YSort, ZIndex};
 use bevy_asset::{load_embedded_asset, AssetEvent, AssetId, Assets, Handle};
+
 use bevy_color::{ColorToComponents, LinearRgba};
+use bevy_core_pipeline::core_2d::Transparent2dSortKey;
 use bevy_core_pipeline::{
     core_2d::{Transparent2d, CORE_2D_DEPTH_FORMAT},
     tonemapping::{
@@ -17,7 +17,7 @@ use bevy_ecs::{
     system::{lifetimeless::*, SystemParamItem, SystemState},
 };
 use bevy_image::{BevyDefault, Image, ImageSampler, TextureAtlasLayout, TextureFormatPixelInfo};
-use bevy_math::{Affine3A, FloatOrd, Quat, Rect, Vec2, Vec4};
+use bevy_math::{Affine3A, Quat, Rect, Vec2, Vec4};
 use bevy_platform::collections::HashMap;
 use bevy_render::view::{RenderVisibleEntities, RetainedViewEntity};
 use bevy_render::{
@@ -41,6 +41,7 @@ use bevy_render::{
 };
 use bevy_transform::components::GlobalTransform;
 use bytemuck::{Pod, Zeroable};
+use core::ops::Range;
 use fixedbitset::FixedBitSet;
 
 #[derive(Resource)]
@@ -343,6 +344,9 @@ pub struct ExtractedSprite {
     pub flip_x: bool,
     pub flip_y: bool,
     pub kind: ExtractedSpriteKind,
+    pub z_index: i32,
+    pub y_sort: bool,
+    pub sort_bias: Option<f32>,
 }
 
 pub enum ExtractedSpriteKind {
@@ -398,13 +402,26 @@ pub fn extract_sprites(
             &GlobalTransform,
             &Anchor,
             Option<&ComputedTextureSlices>,
+            Option<&ZIndex>,
+            Has<YSort>,
+            Option<&SortBias>,
         )>,
     >,
 ) {
     extracted_sprites.sprites.clear();
     extracted_slices.slices.clear();
-    for (main_entity, render_entity, view_visibility, sprite, transform, anchor, slices) in
-        sprite_query.iter()
+    for (
+        main_entity,
+        render_entity,
+        view_visibility,
+        sprite,
+        transform,
+        anchor,
+        slices,
+        z_index,
+        y_sort,
+        sort_bias,
+    ) in sprite_query.iter()
     {
         if !view_visibility.get() {
             continue;
@@ -427,6 +444,9 @@ pub fn extract_sprites(
                 kind: ExtractedSpriteKind::Slices {
                     indices: start..end,
                 },
+                z_index: z_index.cloned().map_or(0, |z| z.0),
+                y_sort,
+                sort_bias: sort_bias.cloned().map(|sb| sb.0),
             });
         } else {
             let atlas_rect = sprite
@@ -460,6 +480,9 @@ pub fn extract_sprites(
                     // Pass the custom size
                     custom_size: sprite.custom_size,
                 },
+                z_index: z_index.cloned().map_or(0, |z| z.0),
+                y_sort,
+                sort_bias: sort_bias.cloned().map(|sb| sb.0),
             });
         }
     }
@@ -595,7 +618,14 @@ pub fn queue_sprites(
             }
 
             // These items will be sorted by depth with other phase items
-            let sort_key = FloatOrd(extracted_sprite.transform.translation().z);
+            let sort_key = Transparent2dSortKey::new(
+                extracted_sprite.z_index,
+                extracted_sprite
+                    .y_sort
+                    .then_some(extracted_sprite.transform.translation().y)
+                    .map(|y| y + extracted_sprite.sort_bias.unwrap_or(0.0))
+                    .or(extracted_sprite.sort_bias),
+            );
 
             // Add the item to the render phase
             transparent_phase.add(Transparent2d {
diff --git a/crates/bevy_text/src/text2d.rs b/crates/bevy_text/src/text2d.rs
index 5069804df8672..9915a0551d59c 100644
--- a/crates/bevy_text/src/text2d.rs
+++ b/crates/bevy_text/src/text2d.rs
@@ -238,6 +238,9 @@ pub fn extract_text2d_sprite(
                     kind: bevy_sprite::ExtractedSpriteKind::Slices {
                         indices: start..end,
                     },
+                    z_index: 0,
+                    y_sort: false,
+                    sort_bias: None,
                 });
                 start = end;
             }
diff --git a/examples/2d/2d_viewport_to_world.rs b/examples/2d/2d_viewport_to_world.rs
index ce611bd4e9c50..0b3309a807936 100644
--- a/examples/2d/2d_viewport_to_world.rs
+++ b/examples/2d/2d_viewport_to_world.rs
@@ -183,6 +183,7 @@ fn setup(
     commands.spawn((
         Mesh2d(meshes.add(Rectangle::new(50000.0, 50000.0))),
         MeshMaterial2d(materials.add(Color::linear_rgb(0.01, 0.01, 0.01))),
-        Transform::from_translation(Vec3::new(0.0, 0.0, -200.0)),
+        Transform::from_translation(Vec3::new(0.0, 0.0, 0.0)),
+        bevy::sprite::ZIndex(-1),
     ));
 }
diff --git a/examples/2d/mesh2d_alpha_mode.rs b/examples/2d/mesh2d_alpha_mode.rs
index 60bb6ef3a6147..b7e766f4f6620 100644
--- a/examples/2d/mesh2d_alpha_mode.rs
+++ b/examples/2d/mesh2d_alpha_mode.rs
@@ -46,7 +46,8 @@ fn setup(
             texture: Some(texture_handle.clone()),
             ..default()
         })),
-        Transform::from_xyz(-300.0, 0.0, 1.0),
+        Transform::from_xyz(-300.0, 0.0, 0.0),
+        bevy::sprite::ZIndex(1),
     ));
     commands.spawn((
         Mesh2d(mesh_handle.clone()),
@@ -56,7 +57,8 @@ fn setup(
             texture: Some(texture_handle.clone()),
             ..default()
         })),
-        Transform::from_xyz(-200.0, 0.0, -1.0),
+        Transform::from_xyz(-200.0, 0.0, 0.0),
+        bevy::sprite::ZIndex(-1),
     ));
 
     // Test the interaction between opaque/mask and transparent meshes
@@ -82,7 +84,8 @@ fn setup(
             texture: Some(texture_handle.clone()),
             ..default()
         })),
-        Transform::from_xyz(300.0, 0.0, 1.0),
+        Transform::from_xyz(300.0, 0.0, 0.0),
+        bevy::sprite::ZIndex(1),
     ));
     commands.spawn((
         Mesh2d(mesh_handle.clone()),
@@ -92,6 +95,7 @@ fn setup(
             texture: Some(texture_handle),
             ..default()
         })),
-        Transform::from_xyz(400.0, 0.0, -1.0),
+        Transform::from_xyz(400.0, 0.0, 0.0),
+        bevy::sprite::ZIndex(-1),
     ));
 }
diff --git a/examples/2d/mesh2d_manual.rs b/examples/2d/mesh2d_manual.rs
index 6354aa468c40d..2ed0ee23c2664 100644
--- a/examples/2d/mesh2d_manual.rs
+++ b/examples/2d/mesh2d_manual.rs
@@ -5,11 +5,12 @@
 //!
 //! [`Material2d`]: bevy::sprite::Material2d
 
+use bevy::core_pipeline::core_2d::Transparent2dSortKey;
 use bevy::{
     asset::weak_handle,
     color::palettes::basic::YELLOW,
     core_pipeline::core_2d::{Transparent2d, CORE_2D_DEPTH_FORMAT},
-    math::{ops, FloatOrd},
+    math::ops,
     prelude::*,
     render::{
         mesh::{Indices, MeshVertexAttribute, RenderMesh},
@@ -367,6 +368,9 @@ pub fn extract_colored_mesh2d(
                 material_bind_group_id: Material2dBindGroupId::default(),
                 automatic_batching: false,
                 tag: 0,
+                z_index: None,
+                y_sort: false,
+                sort_bias: None,
             },
         );
     }
@@ -404,7 +408,6 @@ pub fn queue_colored_mesh2d(
         for (render_entity, visible_entity) in visible_entities.iter::<Mesh2d>() {
             if let Some(mesh_instance) = render_mesh_instances.get(visible_entity) {
                 let mesh2d_handle = mesh_instance.mesh_asset_id;
-                let mesh2d_transforms = &mesh_instance.transforms;
                 // Get our specialized pipeline
                 let mut mesh2d_key = mesh_key;
                 let Some(mesh) = render_meshes.get(mesh2d_handle) else {
@@ -415,14 +418,12 @@ pub fn queue_colored_mesh2d(
                 let pipeline_id =
                     pipelines.specialize(&pipeline_cache, &colored_mesh2d_pipeline, mesh2d_key);
 
-                let mesh_z = mesh2d_transforms.world_from_local.translation.z;
                 transparent_phase.add(Transparent2d {
                     entity: (*render_entity, *visible_entity),
                     draw_function: draw_colored_mesh2d,
                     pipeline: pipeline_id,
-                    // The 2d render items are sorted according to their z value before rendering,
-                    // in order to get correct transparency
-                    sort_key: FloatOrd(mesh_z),
+                    // Transparent 2d items are sorted by a combination of z-index and sort bias
+                    sort_key: Transparent2dSortKey::new(0, None),
                     // This material is not batched
                     batch_range: 0..1,
                     extra_index: PhaseItemExtraIndex::None,
diff --git a/examples/2d/pixel_grid_snap.rs b/examples/2d/pixel_grid_snap.rs
index 4f946f9f941b4..6076d1d40f278 100644
--- a/examples/2d/pixel_grid_snap.rs
+++ b/examples/2d/pixel_grid_snap.rs
@@ -54,7 +54,8 @@ fn setup_sprite(mut commands: Commands, asset_server: Res<AssetServer>) {
     // The sample sprite that will be rendered to the pixel-perfect canvas
     commands.spawn((
         Sprite::from_image(asset_server.load("pixel/bevy_pixel_dark.png")),
-        Transform::from_xyz(-45., 20., 2.),
+        Transform::from_xyz(-45., 20., 0.),
+        bevy::sprite::ZIndex(2),
         Rotate,
         PIXEL_PERFECT_LAYERS,
     ));
@@ -62,7 +63,8 @@ fn setup_sprite(mut commands: Commands, asset_server: Res<AssetServer>) {
     // The sample sprite that will be rendered to the high-res "outer world"
     commands.spawn((
         Sprite::from_image(asset_server.load("pixel/bevy_pixel_light.png")),
-        Transform::from_xyz(-45., -20., 2.),
+        Transform::from_xyz(-45., -20., 0.),
+        bevy::sprite::ZIndex(2),
         Rotate,
         HIGH_RES_LAYERS,
     ));
@@ -77,7 +79,8 @@ fn setup_mesh(
     commands.spawn((
         Mesh2d(meshes.add(Capsule2d::default())),
         MeshMaterial2d(materials.add(Color::BLACK)),
-        Transform::from_xyz(25., 0., 2.).with_scale(Vec3::splat(32.)),
+        Transform::from_xyz(25., 0., 0.).with_scale(Vec3::splat(32.)),
+        bevy::sprite::ZIndex(2),
         Rotate,
         PIXEL_PERFECT_LAYERS,
     ));
diff --git a/examples/2d/sprite_sorting.rs b/examples/2d/sprite_sorting.rs
new file mode 100644
index 0000000000000..d36e5e2d5fef8
--- /dev/null
+++ b/examples/2d/sprite_sorting.rs
@@ -0,0 +1,235 @@
+//! Demonstrates sprite rendering order using `ZIndex`, `YSort`, and `SortBias` components.
+//!
+//! This example shows how different sorting methods interact:
+//! - `bevy::sprite::ZIndex(i32)`: Absolute rendering order (higher = on top)
+//! - `YSort`: Automatic sorting based on Y position (lower Y = behind)
+//! - `SortBias`: Fine-tune sorting without changing actual position
+
+use bevy::sprite::YSort;
+use bevy::{prelude::*, sprite::SortBias};
+
+fn main() {
+    App::new()
+        .add_plugins(DefaultPlugins)
+        .add_systems(Startup, setup)
+        .add_systems(Update, (move_sprites, update_info_text))
+        .run();
+}
+
+#[derive(Component)]
+struct Movable {
+    label: String,
+}
+
+#[derive(Component)]
+struct InfoText;
+
+fn setup(mut commands: Commands) {
+    commands.spawn(Camera2d);
+
+    commands.spawn((
+        Text::new(
+            "WASD: Move white sprite | Q/E: Adjust sort bias\n\
+            Hover over sprites to see their sorting properties",
+        ),
+        Node {
+            position_type: PositionType::Absolute,
+            top: Val::Px(12.0),
+            left: Val::Px(12.0),
+            ..default()
+        },
+        InfoText,
+    ));
+
+    // Left
+
+    commands.spawn((
+        Sprite {
+            color: Color::srgb(0.8, 0.2, 0.2),
+            custom_size: Some(Vec2::splat(60.0)),
+            ..default()
+        },
+        Transform::from_translation(Vec3::new(-300.0, 50.0, 0.0)),
+        bevy::sprite::ZIndex(10),
+    ));
+
+    commands.spawn((
+        Sprite {
+            color: Color::srgb(0.2, 0.2, 0.8),
+            custom_size: Some(Vec2::splat(60.0)),
+            ..default()
+        },
+        Transform::from_translation(Vec3::new(-280.0, 30.0, 0.0)),
+        bevy::sprite::ZIndex(5),
+    ));
+
+    commands.spawn((
+        Sprite {
+            color: Color::srgb(0.2, 0.8, 0.2),
+            custom_size: Some(Vec2::splat(60.0)),
+            ..default()
+        },
+        Transform::from_translation(Vec3::new(-260.0, 70.0, 0.0)),
+        bevy::sprite::ZIndex(15),
+    ));
+
+    // Center
+
+    for i in 0..3 {
+        let y = -50.0 + i as f32 * 40.0;
+        commands.spawn((
+            Sprite {
+                color: Color::srgb(0.9, 0.5, 0.1),
+                custom_size: Some(Vec2::splat(60.0)),
+                ..default()
+            },
+            Transform::from_translation(Vec3::new(-50.0 + i as f32 * 20.0, y, 0.0)),
+            YSort,
+        ));
+    }
+
+    // Right
+
+    commands.spawn((
+        Sprite {
+            color: Color::srgb(0.6, 0.2, 0.8),
+            custom_size: Some(Vec2::splat(60.0)),
+            ..default()
+        },
+        Transform::from_translation(Vec3::new(200.0, 0.0, 0.0)),
+        YSort,
+        SortBias(-20.0),
+    ));
+
+    commands.spawn((
+        Sprite {
+            color: Color::srgb(0.2, 0.8, 0.8),
+            custom_size: Some(Vec2::splat(60.0)),
+            ..default()
+        },
+        Transform::from_translation(Vec3::new(220.0, 10.0, 0.0)),
+        YSort,
+        SortBias(20.0),
+    ));
+
+    commands.spawn((
+        Sprite {
+            color: Color::srgb(0.8, 0.8, 0.2),
+            custom_size: Some(Vec2::splat(60.0)),
+            ..default()
+        },
+        Transform::from_translation(Vec3::new(240.0, 0.0, 0.0)),
+        YSort,
+    ));
+
+    // Moveable sprite
+    commands.spawn((
+        Sprite {
+            color: Color::WHITE,
+            custom_size: Some(Vec2::splat(50.0)),
+            ..default()
+        },
+        Transform::from_translation(Vec3::new(0.0, 0.0, 0.0)),
+        YSort,
+        SortBias(0.0),
+        Movable {
+            label: "Movable (YSort + Bias: 0)".to_string(),
+        },
+    ));
+
+    // Background grid
+    for x in -4..=4 {
+        for y in -3..=3 {
+            commands.spawn((
+                Sprite {
+                    color: Color::srgba(0.3, 0.3, 0.3, 0.1),
+                    custom_size: Some(Vec2::splat(30.0)),
+                    ..default()
+                },
+                Transform::from_translation(Vec3::new(x as f32 * 100.0, y as f32 * 100.0, 0.0)),
+                bevy::sprite::ZIndex(-100), // Always behind everything
+            ));
+        }
+    }
+
+    // Labels
+
+    commands.spawn((
+        Text::new("ZIndex Only"),
+        Node {
+            position_type: PositionType::Absolute,
+            left: Val::Px(300.0),
+            bottom: Val::Px(50.0),
+            ..default()
+        },
+        bevy::sprite::ZIndex(1000),
+    ));
+
+    commands.spawn((
+        Text::new("YSort Only"),
+        Node {
+            position_type: PositionType::Absolute,
+            left: Val::Px(550.0),
+            bottom: Val::Px(50.0),
+            ..default()
+        },
+        bevy::sprite::ZIndex(1000),
+    ));
+
+    commands.spawn((
+        Text::new("YSort + SortBias"),
+        Node {
+            position_type: PositionType::Absolute,
+            right: Val::Px(300.0),
+            bottom: Val::Px(50.0),
+            ..default()
+        },
+        bevy::sprite::ZIndex(1000),
+    ));
+}
+
+fn move_sprites(
+    mut query: Query<(&mut Transform, &mut SortBias, &mut Movable)>,
+    keyboard: Res<ButtonInput<KeyCode>>,
+    time: Res<Time>,
+) {
+    let speed = 150.0;
+    let bias_speed = 50.0;
+    let delta = time.delta_secs();
+
+    for (mut transform, mut sort_bias, mut movable) in &mut query {
+        if keyboard.pressed(KeyCode::KeyA) {
+            transform.translation.x -= speed * delta;
+        }
+        if keyboard.pressed(KeyCode::KeyD) {
+            transform.translation.x += speed * delta;
+        }
+        if keyboard.pressed(KeyCode::KeyW) {
+            transform.translation.y += speed * delta;
+        }
+        if keyboard.pressed(KeyCode::KeyS) {
+            transform.translation.y -= speed * delta;
+        }
+
+        // Adjust sort bias
+        if keyboard.pressed(KeyCode::KeyQ) {
+            sort_bias.0 -= bias_speed * delta;
+        }
+        if keyboard.pressed(KeyCode::KeyE) {
+            sort_bias.0 += bias_speed * delta;
+        }
+
+        // Update label
+        movable.label = format!(
+            "Movable (Y: {:.0}, Bias: {:.0})",
+            transform.translation.y, sort_bias.0
+        );
+    }
+}
+
+fn update_info_text(mut text: Single<&mut Text, With<InfoText>>, movable: Single<&Movable>) {
+    text.0 = format!(
+        "WASD: Move white sprite | Q/E: Adjust sort bias\n{}",
+        movable.label
+    );
+}
diff --git a/examples/README.md b/examples/README.md
index 4f5030563aa7d..2411972462293 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -126,6 +126,7 @@ Example | Description
 [Sprite Scale](../examples/2d/sprite_scale.rs) | Shows how a sprite can be scaled into a rectangle while keeping the aspect ratio
 [Sprite Sheet](../examples/2d/sprite_sheet.rs) | Renders an animated sprite
 [Sprite Slice](../examples/2d/sprite_slice.rs) | Showcases slicing sprites into sections that can be scaled independently via the 9-patch technique
+[Sprite Sorting](../examples/2d/sprite_sorting.rs) | Demonstrates how to sort sprites
 [Sprite Tile](../examples/2d/sprite_tile.rs) | Renders a sprite tiled in a grid
 [Text 2D](../examples/2d/text2d.rs) | Generates text in 2D
 [Texture Atlas](../examples/2d/texture_atlas.rs) | Generates a texture atlas (sprite sheet) from individual sprites