@@ -505,6 +505,12 @@ impl CalendarItem {
505505 children : vec ! [ ] ,
506506 }
507507 }
508+ // Keep the default visible label available to assistive technology even
509+ // when a presentation assigns a role that does not derive a name from children.
510+ fn with_label ( self , label : SharedString ) -> Self {
511+ self . aria_label ( label. clone ( ) ) . child ( label)
512+ }
513+
508514 pub fn item_state ( & self ) -> CalendarItemState {
509515 self . state
510516 }
@@ -605,7 +611,12 @@ impl Calendar {
605611 cx : & mut App ,
606612 ) -> AnyElement {
607613 let label = ( self . label ) ( state. kind ( ) , value) ;
608- ( self . item ) ( CalendarItem :: new ( id, state) . child ( label) , state, window, cx)
614+ ( self . item ) (
615+ CalendarItem :: new ( id, state) . with_label ( label) ,
616+ state,
617+ window,
618+ cx,
619+ )
609620 }
610621}
611622impl Styled for Calendar {
@@ -624,7 +635,8 @@ impl RenderOnce for Calendar {
624635 let st = CalendarItemState :: new ( CalendarItemKind :: Previous ) . disabled (
625636 view. is_month ( ) || ( view. is_year ( ) && !self . state . read ( cx) . has_prev_year_page ( ) ) ,
626637 ) ;
627- let mut item = CalendarItem :: new ( "calendar-prev" , st) . child ( ( self . label ) ( st. kind ( ) , 0 ) ) ;
638+ let mut item =
639+ CalendarItem :: new ( "calendar-prev" , st) . with_label ( ( self . label ) ( st. kind ( ) , 0 ) ) ;
628640 if !st. is_disabled ( ) {
629641 let entity = self . state . clone ( ) ;
630642 item = item. on_click ( move |_, _window, cx| {
@@ -652,7 +664,7 @@ impl RenderOnce for Calendar {
652664 let st = CalendarItemState :: new ( kind) . active ( active) ;
653665 let entity = self . state . clone ( ) ;
654666 let mut item = CalendarItem :: new ( format ! ( "calendar-{kind:?}" ) , st)
655- . child ( ( self . label ) ( kind, value) ) ;
667+ . with_label ( ( self . label ) ( kind, value) ) ;
656668 item = item. on_click ( move |_, _, cx| {
657669 entity. update ( cx, |s, cx| {
658670 s. set_view (
@@ -692,7 +704,8 @@ impl RenderOnce for Calendar {
692704 let st = CalendarItemState :: new ( CalendarItemKind :: Next ) . disabled (
693705 view. is_month ( ) || ( view. is_year ( ) && !self . state . read ( cx) . has_next_year_page ( ) ) ,
694706 ) ;
695- let mut item = CalendarItem :: new ( "calendar-next" , st) . child ( ( self . label ) ( st. kind ( ) , 0 ) ) ;
707+ let mut item =
708+ CalendarItem :: new ( "calendar-next" , st) . with_label ( ( self . label ) ( st. kind ( ) , 0 ) ) ;
696709 if !st. is_disabled ( ) {
697710 let entity = self . state . clone ( ) ;
698711 item = item. on_click ( move |_, _, cx| {
@@ -752,7 +765,8 @@ impl RenderOnce for Calendar {
752765 } ;
753766 let mut item =
754767 CalendarItem :: new ( format ! ( "calendar-{date}-{offset}-{week_index}" ) , st)
755- . child ( ( self . label ) ( st. kind ( ) , date. day ( ) as i32 ) ) ;
768+ . with_label ( ( self . label ) ( st. kind ( ) , date. day ( ) as i32 ) )
769+ . aria_label ( date. to_string ( ) ) ;
756770 if !st. is_disabled ( ) {
757771 let entity = self . state . clone ( ) ;
758772 item = item. on_click ( move |_, _, cx| {
@@ -773,7 +787,7 @@ impl RenderOnce for Calendar {
773787 let st = CalendarItemState :: new ( CalendarItemKind :: Month ) . active ( month == current) ;
774788 let entity = self . state . clone ( ) ;
775789 let item = CalendarItem :: new ( format ! ( "calendar-month-{month}" ) , st)
776- . child ( ( self . label ) ( st. kind ( ) , month as i32 ) )
790+ . with_label ( ( self . label ) ( st. kind ( ) , month as i32 ) )
777791 . on_click ( move |_, _, cx| {
778792 entity. update ( cx, |s, cx| {
779793 s. select_month ( month) ;
@@ -789,7 +803,7 @@ impl RenderOnce for Calendar {
789803 let st = CalendarItemState :: new ( CalendarItemKind :: Year ) . active ( year == current) ;
790804 let entity = self . state . clone ( ) ;
791805 let item = CalendarItem :: new ( format ! ( "calendar-year-{year}" ) , st)
792- . child ( ( self . label ) ( st. kind ( ) , year) )
806+ . with_label ( ( self . label ) ( st. kind ( ) , year) )
793807 . on_click ( move |_, _, cx| {
794808 entity. update ( cx, |s, cx| {
795809 s. select_year ( year) ;
@@ -838,6 +852,55 @@ mod tests {
838852
839853 use super :: * ;
840854
855+ #[ gpui:: test]
856+ fn calendar_items_expose_names_in_every_view ( cx : & mut gpui:: TestAppContext ) {
857+ use gpui:: { Element as _, accesskit} ;
858+ struct Names {
859+ calendar : Entity < CalendarState > ,
860+ labels : Rc < RefCell < Vec < ( CalendarItemKind , String ) > > > ,
861+ }
862+ impl Render for Names {
863+ fn render ( & mut self , _: & mut Window , _: & mut Context < Self > ) -> impl IntoElement {
864+ let labels = self . labels . clone ( ) ;
865+ labels. borrow_mut ( ) . clear ( ) ;
866+ Calendar :: new ( "named-calendar" , & self . calendar ) . item ( move |item, state, _, _| {
867+ let mut node = accesskit:: Node :: new ( accesskit:: Role :: Button ) ;
868+ item. base . write_a11y_info ( & mut node) ;
869+ labels
870+ . borrow_mut ( )
871+ . push ( ( state. kind ( ) , node. label ( ) . unwrap_or ( "" ) . to_string ( ) ) ) ;
872+ item. into_any_element ( )
873+ } )
874+ }
875+ }
876+ let ( view, cx) = cx. add_window_view ( |window, cx| Names {
877+ calendar : cx. new ( |cx| CalendarState :: new ( window, cx) ) ,
878+ labels : Rc :: new ( RefCell :: new ( Vec :: new ( ) ) ) ,
879+ } ) ;
880+ for mode in [ CalendarView :: Day , CalendarView :: Month , CalendarView :: Year ] {
881+ cx. update ( |window, cx| {
882+ view. read ( cx) . calendar . clone ( ) . update ( cx, |state, cx| {
883+ state. apply_date ( Date :: Single ( Some (
884+ NaiveDate :: from_ymd_opt ( 2026 , 9 , 7 ) . unwrap ( ) ,
885+ ) ) ) ;
886+ state. set_view ( mode) ;
887+ cx. notify ( ) ;
888+ } ) ;
889+ window. draw ( cx) . clear ( cx) ;
890+ let labels = view. read ( cx) . labels . borrow ( ) ;
891+ assert ! ( !labels. is_empty( ) ) ;
892+ for ( kind, label) in labels. iter ( ) {
893+ assert ! ( !label. is_empty( ) , "missing name for {kind:?}" ) ;
894+ }
895+ if mode. is_day ( ) {
896+ assert ! ( labels. iter( ) . any(
897+ |( kind, label) | * kind == CalendarItemKind :: Day && label == "2026-09-07"
898+ ) ) ;
899+ }
900+ } ) ;
901+ }
902+ }
903+
841904 struct EventHarness {
842905 calendar : Entity < CalendarState > ,
843906 events : Rc < RefCell < Vec < Date > > > ,
0 commit comments