{"$schema": "https://raw.githubusercontent.com/guitaripod/wwdc-sessions/master/schema/transcript.schema.json", "id": "wwdc2026-220", "language": "eng", "source": "https://developer.apple.com/videos/play/wwdc2026/220", "wordCount": 2192, "segments": [{"start": 7.0, "text": "Hi, I'm Khin, a software engineer on the accessibility team."}, {"start": 11.0, "text": "Custom UI controls empower people to do unique and creative things in your app."}, {"start": 16.0, "text": "They let people use gestures"}, {"start": 18.0, "text": "and interactions that go beyond standard controls."}, {"start": 23.0, "text": "At Apple, we believe that technology works best when it works for everyone."}, {"start": 29.0, "text": "That means bringing a great experience to people using assistive technologies"}, {"start": 34.0, "text": "like VoiceOver, Switch Control, and more."}, {"start": 38.0, "text": "Making control accessible ensures that everyone gets access to the very thing"}, {"start": 43.0, "text": "your app was built to do."}, {"start": 46.0, "text": "In this session, I'll explore how you can make any control in your app accessible."}, {"start": 51.0, "text": "First, I'll start by explaining some guiding principles you can use."}, {"start": 56.0, "text": "And then I'll show you how those get applied to a few complex controls."}, {"start": 62.0, "text": "Now let's talk about the guiding principles."}, {"start": 65.0, "text": "Consider this slider."}, {"start": 67.0, "text": "It's a standard slider from SwiftUI."}, {"start": 70.0, "text": "Even if this is your first time encountering it,"}, {"start": 73.0, "text": "you may already know a lot about it from its appearance."}, {"start": 76.0, "text": "It contains a track and on top of it, a handle."}, {"start": 81.0, "text": "The handle appears about halfway along its track."}, {"start": 84.0, "text": "The handle also seems like someone can grab it,"}, {"start": 87.0, "text": "so it can be moved by dragging."}, {"start": 90.0, "text": "Using this visual information, you gather a lot of implicit cues about this control."}, {"start": 95.0, "text": "You can tell that this control represents a continuous value."}, {"start": 100.0, "text": "You also know what the current value is."}, {"start": 102.0, "text": "In this case, about half of the maximum."}, {"start": 106.0, "text": "You can also identify the gesture for actions, like changing its value."}, {"start": 111.0, "text": "The handle appears to be draggable, and when you do,"}, {"start": 114.0, "text": "the feedback is immediate,"}, {"start": 116.0, "text": "the track fills, and the handle moves,"}, {"start": 119.0, "text": "giving clear feedback that something happened."}, {"start": 122.0, "text": "Nobody had to explain any of that."}, {"start": 124.0, "text": "It was understood at a glance."}, {"start": 126.0, "text": "Your brain processed the shape, the position, the expected behavior,"}, {"start": 131.0, "text": "all in a fraction of a second."}, {"start": 134.0, "text": "That's how powerful visual information is."}, {"start": 137.0, "text": "Now, what if someone can't see the screen?"}, {"start": 140.0, "text": "The track, the handle, the position, none of it is known."}, {"start": 145.0, "text": "Not everyone will have access to visual information,"}, {"start": 148.0, "text": "so it's important to consider"}, {"start": 150.0, "text": "how people using assistive technologies will interact with controls like these."}, {"start": 156.0, "text": "Here's how VoiceOver describes this slider."}, {"start": 159.0, "text": "VoiceOver is a built-in screen reader on Apple platforms."}, {"start": 164.0, "text": "It lets people who are blind or low vision"}, {"start": 167.0, "text": "use gestures to interact with their device."}, {"start": 170.0, "text": "\"Brightness, 50%, adjustable.\""}, {"start": 174.0, "text": "\"Swipe up or down with one finger to adjust the value.\""}, {"start": 178.0, "text": "Using VoiceOver, someone is able to understand what the slider controls,"}, {"start": 183.0, "text": "says the label, \"Brightness\"."}, {"start": 185.0, "text": "It also indicates that this control has a value and it's currently set to 50%."}, {"start": 190.0, "text": "It's also clear what actions someone can take."}, {"start": 194.0, "text": "It reads adjustable and provides a hint on how to adjust the value."}, {"start": 199.0, "text": "As the value changes, announces the new value in real time."}, {"start": 204.0, "text": "That's the feedback someone gets."}, {"start": 207.0, "text": "Someone using VoiceOver can get a great experience,"}, {"start": 210.0, "text": "even without access to the control 's visuals."}, {"start": 214.0, "text": "Consider the information people get from the visual form of a control."}, {"start": 218.0, "text": "Use each of these as your guiding principle"}, {"start": 221.0, "text": "for your own accessibility experience."}, {"start": 223.0, "text": "Ensure that the purpose of the control is understood."}, {"start": 227.0, "text": "If it expresses a value, make that value available to assistive technologies."}, {"start": 232.0, "text": "Make it clear what action someone can take and how they get feedback"}, {"start": 236.0, "text": "as they use the control."}, {"start": 239.0, "text": "Here's another example,"}, {"start": 241.0, "text": "a custom control for my coffee maker's app."}, {"start": 244.0, "text": "Some mornings are full cup mornings,"}, {"start": 247.0, "text": "some mornings are half."}, {"start": 249.0, "text": "Depends if I wake up to my alarm or to screaming cats."}, {"start": 252.0, "text": "So, I build an app that lets me control the amount of coffee brewed in"}, {"start": 257.0, "text": "with one simple gesture."}, {"start": 259.0, "text": "I drag up for more coffee and drag down for less."}, {"start": 262.0, "text": "The fill level represents how many ounces to be brewed."}, {"start": 266.0, "text": "Currently, this control doesn't provide any additional information"}, {"start": 270.0, "text": "for accessibility."}, {"start": 272.0, "text": "As I swipe right to select the control, VoiceOver doesn't describe what it does"}, {"start": 276.0, "text": "or how someone changes its value."}, {"start": 279.0, "text": "\"Settings.\" \"Button.\""}, {"start": 282.0, "text": "\"6 ounces.\""}, {"start": 284.0, "text": "\"Drag up or down on the cup.\""}, {"start": 287.0, "text": "It's not clear how to interact with this control yet."}, {"start": 290.0, "text": "But don't worry."}, {"start": 292.0, "text": "This can be proved in a few simple steps."}, {"start": 295.0, "text": "To do this, I'll revisit the guiding principles I explored earlier."}, {"start": 301.0, "text": "Here's the implementation for the CoffeeDispenserView."}, {"start": 304.0, "text": "Currently, it just declares a coffee level as a State and passes it to the slider."}, {"start": 310.0, "text": "First, I'll mark the slider as an accessibility element."}, {"start": 314.0, "text": "To give it a clear purpose, I use the accessibilityLabel modifier"}, {"start": 319.0, "text": "to call it \"Coffee Dispenser\"."}, {"start": 321.0, "text": "And then I'll use the accessibilityValue modifier"}, {"start": 324.0, "text": "to announce the current fill level."}, {"start": 328.0, "text": "I also want to give VoiceOver the ability to adjust the coffee slider"}, {"start": 332.0, "text": "the same way a built-in slider does."}, {"start": 334.0, "text": "I'll add the same interaction to this control."}, {"start": 337.0, "text": "I start by adding a trait call .adjustable."}, {"start": 340.0, "text": "This tells VoiceOver that this control can be adjusted with a swipe."}, {"start": 345.0, "text": "Then I define what each swipe does"}, {"start": 347.0, "text": "with the .accessibilityAdjustableAction Modifier."}, {"start": 351.0, "text": "The closure provides a direction parameter,"}, {"start": 354.0, "text": "either .increment or .decrement."}, {"start": 356.0, "text": "The closure should handle each case."}, {"start": 360.0, "text": "With those changes, here's the new VoiceOver experience."}, {"start": 364.0, "text": "I'll swipe right to select the control and then swipe up and down"}, {"start": 368.0, "text": "to adjust the amount of coffee I want."}, {"start": 371.0, "text": "\"Coffee dispenser, 6 ounces, adjustable.\""}, {"start": 375.0, "text": "\"Swipe up or down with one finger to adjust the value.\""}, {"start": 380.0, "text": "\"7 ounces.\""}, {"start": 382.0, "text": "\"8 ounces.\""}, {"start": 383.0, "text": "\"7 ounces.\""}, {"start": 384.0, "text": "\"6 ounces.\""}, {"start": 386.0, "text": "People can now adjust the coffee level using VoiceOver one ounce at a time."}, {"start": 392.0, "text": "But what if someone is feeling particular and wants to adjust by half an ounce?"}, {"start": 397.0, "text": "For precise control,"}, {"start": 398.0, "text": "VoiceOver has that build-in capability called the passthrough gesture."}, {"start": 403.0, "text": "To perform a passthrough gesture, someone using VoiceOver"}, {"start": 407.0, "text": "can double tap and hold to activate."}, {"start": 409.0, "text": "The gesture starts at your control's accessibilityActivationPoint."}, {"start": 414.0, "text": "As the person's finger moves around,"}, {"start": 416.0, "text": "VoiceOver sends touch events directly to the control."}, {"start": 420.0, "text": "This gives someone more precise, fine-grained control over their value."}, {"start": 425.0, "text": "For the coffee slider, the accessibility activation point"}, {"start": 428.0, "text": "is at the center by default."}, {"start": 430.0, "text": "I'll set it to match the current fill level."}, {"start": 433.0, "text": "This way, the gesture always starts right at the coffee level,"}, {"start": 437.0, "text": "giving room to adjust whichever direction makes sense."}, {"start": 442.0, "text": "It's also important to give people feedback during the passthrough gesture."}, {"start": 447.0, "text": "To do this, I post the accessibility announcement"}, {"start": 450.0, "text": "when the value changes."}, {"start": 451.0, "text": "But not every change though."}, {"start": 453.0, "text": "That would get noisy."}, {"start": 454.0, "text": "Instead, I track what was last spoken and when."}, {"start": 458.0, "text": "If the value has actually changed,"}, {"start": 460.0, "text": "and at least .3 seconds have passed,"}, {"start": 463.0, "text": "then I announce."}, {"start": 464.0, "text": "Otherwise, I skip it."}, {"start": 466.0, "text": "This way, people using VoiceOver can hear meaningful updates."}, {"start": 472.0, "text": "Now I'll double tap and hold,"}, {"start": 474.0, "text": "then move my finger up to try a passthrough gesture"}, {"start": 478.0, "text": "to fill my coffee level up to 9.5 ounces."}, {"start": 482.0, "text": "\"Coffee dispenser, 6 ounces, adjustable.\""}, {"start": 486.0, "text": "\"Swipe up or down with one finger to adjust the value.\""}, {"start": 491.0, "text": "\"Six... 6.4 ounces.\""}, {"start": 493.0, "text": "\"Six... Six... Se... Se... Seve... 8.3 oun... 8.4 ounces.\""}, {"start": 499.0, "text": "\"8.5 oun... 8.6 ounces.\""}, {"start": 501.0, "text": "\"8.7 ounces.\""}, {"start": 503.0, "text": "\"8.8 ou... 9... 9.5 ounces.\""}, {"start": 508.0, "text": "That's great. The controller is now draggable and gives meaningful updates."}, {"start": 512.0, "text": "The experience now delivers on the guiding principles"}, {"start": 516.0, "text": "I shared earlier"}, {"start": 517.0, "text": "with the label, value, actions and announcements."}, {"start": 522.0, "text": "Now that I've explored basic custom control,"}, {"start": 524.0, "text": "let's take a look at a few more complex examples."}, {"start": 529.0, "text": "Here is one of my favorite features on iOS,"}, {"start": 531.0, "text": "Background Sounds."}, {"start": 533.0, "text": "Let's me play ambient audio, like rain or ocean waves,"}, {"start": 537.0, "text": "to help me focus and relax."}, {"start": 540.0, "text": "You can find it right in the Accessibility Settings."}, {"start": 544.0, "text": "Inside settings, there is an equalizer control."}, {"start": 547.0, "text": "It's a two-dimensional pad."}, {"start": 548.0, "text": "You can move the handle at the center anywhere on the surface."}, {"start": 553.0, "text": "This pad has two dimensions you can move around to adjust the sound's tone."}, {"start": 557.0, "text": "Visually, there are frequency and amplitude symbols for each axis."}, {"start": 563.0, "text": "When you grab the handle, you can actually explore"}, {"start": 565.0, "text": "the space of these two values at the same time."}, {"start": 570.0, "text": "With the slider, the adjustable traits provide two actions,"}, {"start": 574.0, "text": "increment and decrement,"}, {"start": 576.0, "text": "on a single axis."}, {"start": 578.0, "text": "But with this control, there are two axes,"}, {"start": 582.0, "text": "horizontal and vertical."}, {"start": 584.0, "text": "Adding the adjustable trait would only cover one direction,"}, {"start": 588.0, "text": "so it's not the ideal solution."}, {"start": 591.0, "text": "This is where custom actions come in."}, {"start": 593.0, "text": "Custom actions let you expose common actions of a control."}, {"start": 597.0, "text": "Each action has a label that VoiceOver reads out loud"}, {"start": 601.0, "text": "and a closure that runs when activated."}, {"start": 604.0, "text": "Unlike the adjustable action,"}, {"start": 606.0, "text": "custom actions support any operation that you defined,"}, {"start": 609.0, "text": "not limited to a single axis."}, {"start": 612.0, "text": "For this equalizer pad, here's how custom actions are added."}, {"start": 616.0, "text": "On the equalizer pad view,"}, {"start": 618.0, "text": "the accessibilityAction modifier is added four times."}, {"start": 622.0, "text": "Each action has a descriptive name."}, {"start": 624.0, "text": "Move up, move right, move down, move left."}, {"start": 628.0, "text": "Each one moves a single axis by a fixed step"}, {"start": 632.0, "text": "clamped within this range."}, {"start": 634.0, "text": "These actions make it possible to explore the space"}, {"start": 637.0, "text": "really just by performing actions that are already familiar to people"}, {"start": 641.0, "text": "who rely on assistive technologies."}, {"start": 645.0, "text": "Here's the experience of using equalizer pad with custom actions."}, {"start": 649.0, "text": "I'll swipe up and down to select an action and double tap to perform it."}, {"start": 654.0, "text": "\"Filter chart, frequency 0, amplitude 10.\""}, {"start": 658.0, "text": "\"Double tap and hold, then drag to adjust filters.\""}, {"start": 661.0, "text": "\"The bounds of the chart axis are set to -100 to 100.\""}, {"start": 665.0, "text": "\"Swipe up or down to select a custom action.\""}, {"start": 668.0, "text": "\"Then double tap to activate.\""}, {"start": 670.0, "text": "\"Move down.\""}, {"start": 671.0, "text": "\"Move up.\""}, {"start": 672.0, "text": "\"Move right.\""}, {"start": 673.0, "text": "\"Move left.\""}, {"start": 675.0, "text": "\"Move right.\""}, {"start": 676.0, "text": "\"Move up.\""}, {"start": 677.0, "text": "\"Frequency 0, amplitude 20.\""}, {"start": 680.0, "text": "\"Frequency 0, amplitude 30.\""}, {"start": 683.0, "text": "\"Frequency 0, amplitude 40.\""}, {"start": 687.0, "text": "Someone can navigate to 2D space through actions,"}, {"start": 691.0, "text": "and the sound itself provides clear feedback."}, {"start": 695.0, "text": "The equalizer pad is a great example of how the platform"}, {"start": 700.0, "text": "applies each of the guiding principles to its own experiences."}, {"start": 704.0, "text": "Now, I want to show you an app that I've been working on"}, {"start": 707.0, "text": "with its own custom control."}, {"start": 710.0, "text": "If you're anything like me, you love your pets"}, {"start": 712.0, "text": "and when you're away at work, you really miss them."}, {"start": 715.0, "text": "To keep me company during the day,"}, {"start": 717.0, "text": "I built an app that lets me play with my cat"}, {"start": 719.0, "text": "right from my screen."}, {"start": 721.0, "text": "Pet it, and it purrs. Tap it, and get a meow."}, {"start": 725.0, "text": "Pinch it, and well, it's a cat — so it hisses right back at you!"}, {"start": 729.0, "text": "Just like the real thing."}, {"start": 731.0, "text": "I haven't added any accessibility support for this control yet."}, {"start": 735.0, "text": "I'll check out the default experience with VoiceOver."}, {"start": 739.0, "text": "\"Cat fill. Space. Image.\""}, {"start": 742.0, "text": "\"Touch the cat to interact.\""}, {"start": 745.0, "text": "It just says an image on the screen."}, {"start": 747.0, "text": "Patting, tapping, and pinching are all the gestures that this control supports,"}, {"start": 753.0, "text": "but VoiceOver doesn't know they exist."}, {"start": 756.0, "text": "All of that charm, the purring, the kneading, the angry meow,"}, {"start": 761.0, "text": "it's all there,"}, {"start": 763.0, "text": "waiting to be shared with people who rely on assistive technologies."}, {"start": 767.0, "text": "VirtualCat is a Swift UI view containing the interactive cat surface."}, {"start": 772.0, "text": "To express the control purpose,"}, {"start": 774.0, "text": "I add an accessibility label that reads \"Virtual Cat\"."}, {"start": 778.0, "text": "Then I set the accessibility value to the cat's current reaction."}, {"start": 783.0, "text": "Now I need a way to explore the gestures for interacting with a cat to VoiceOver."}, {"start": 789.0, "text": "People could use the pass-through gesture, but it might not be the best fit here."}, {"start": 793.0, "text": "For example, people might want to do this action over and over again"}, {"start": 798.0, "text": "or use multiple gestures."}, {"start": 800.0, "text": "So for this control, I'll use direct touch."}, {"start": 803.0, "text": "The direct touch API marks a region of the screen as a direct touch area."}, {"start": 808.0, "text": "When you add the allowsDirectInteraction trait,"}, {"start": 812.0, "text": "touch events pass straight to the control,"}, {"start": 814.0, "text": "rather than being processed by VoiceOver."}, {"start": 818.0, "text": "This way, people can interact with the control directly,"}, {"start": 821.0, "text": "allowing them to use all the gestures it supports."}, {"start": 825.0, "text": "You can customize this behavior with direct touch options."}, {"start": 829.0, "text": "First option is .requiresActivation."}, {"start": 832.0, "text": "When set, the control won't respond to direct touch"}, {"start": 835.0, "text": "until someone double taps."}, {"start": 837.0, "text": "This allows someone to drag their finger across the screen"}, {"start": 841.0, "text": "without accidentally activating it."}, {"start": 843.0, "text": "And unlike the passthrough gesture, direct touch stays active"}, {"start": 847.0, "text": "until focus moves to another element."}, {"start": 851.0, "text": "Another option is .silentOnTouch."}, {"start": 853.0, "text": "When set, VoiceOver stays completely silent"}, {"start": 856.0, "text": "when someone touches the area."}, {"start": 858.0, "text": "This is for controls that provide their own audio feedback"}, {"start": 862.0, "text": "where VoiceOver speech"}, {"start": 863.0, "text": "would talk over the audio from the control itself."}, {"start": 866.0, "text": "To make this control interactive, I'll add the .accessibilityDirectTouch modifier"}, {"start": 872.0, "text": "with .requiresActivation option."}, {"start": 875.0, "text": "Keep in mind, not everyone is going to be able to perform direct touch gestures."}, {"start": 880.0, "text": "So whenever possible, try to expose other ways to interact with the control."}, {"start": 885.0, "text": "For example, using custom actions."}, {"start": 889.0, "text": "Here's the updated experience with VoiceOver."}, {"start": 892.0, "text": "I can double tap to activate the direct touch"}, {"start": 894.0, "text": "and then I'll try patting, tapping, and pinching the cat."}, {"start": 901.0, "text": "\"Virtual cat, sleeping.\""}, {"start": 904.0, "text": "\"Activate to start direct touch interaction.\""}, {"start": 906.0, "text": "\"Actions available.\""}, {"start": 909.0, "text": "\"Virtual cat.\""}, {"start": 910.0, "text": "\"Sleeping.\""}, {"start": 922.0, "text": "With these changes, someone using VoiceOver"}, {"start": 924.0, "text": "knows the description of the control and gestures to perform a direct touch."}, {"start": 929.0, "text": "They also get feedback from the interactions."}, {"start": 932.0, "text": "This brings everything that's delightful about this app"}, {"start": 935.0, "text": "to people using assistive technologies."}, {"start": 938.0, "text": "Make your own custom interactive controls"}, {"start": 941.0, "text": "accessible to everyone."}, {"start": 943.0, "text": "Turn on VoiceOver, open your app to find opportunities for improvement."}, {"start": 948.0, "text": "When building custom controls, consider whether people can understand"}, {"start": 952.0, "text": "the control purpose, value, actions, and feedback."}, {"start": 956.0, "text": "Think about direct touch when your controls rely on gestures"}, {"start": 960.0, "text": "which pass-through isn't best supported for,"}, {"start": 962.0, "text": "and provide custom actions whenever possible."}, {"start": 966.0, "text": "This way, people using Switch Control, Voice Control,"}, {"start": 970.0, "text": "and other assistive technologies can access the same interactions."}, {"start": 975.0, "text": "Thanks for watching."}]}