A Month of Accessible Elm
bit.ly/elm-a11y
Hi, I'm Brooke!
Hi, I'm Brooke!
📕
This is the story of my foray into Accessibility in Elm.
But first... habits.
Big goals
2019: be healthy?
Top 10 Resolutions
1. Diet or eat healthier
2. Exercise more
3. Lose weight
4. Save more and spend less
5. Learn a new skill or hobby
6. Quit smoking
7. Read more
8. Find another job
9. Drink less alcohol
10. Spend more time with family and friends
Success Rates
60% make resolutions
8% keep them
Why aren't my resolutions effective?
- 🤷♀️ Too ambiguous
- ⏳ Too long-term
Big goals ➡️ approachable habits
➡️
Small habit #1
Lift weights twice a week for just one month 🏋️♀️
Hey... that wasn't so bad.
👻 Big goals are scary.
🐻 Little habits are approachable.
Okay, Brooke. That's not revolutionary.
Bring on the monthly habits!
Good habits are...
- Purposeful
- Specific
- Time limited
Habit log
- ✅ January: Floss every night
- 🚫 February: Bike or walk to 80% of places in a 10 mile radius
- ✅ March: Take my vitamins daily
Overall, small habits worked!
Excellence
... another big goal
If we want our product to be excellent, our site should be accessible to a diverse range of students.
Web accessibility... what's that?
Accessibility... for whom?
For scale, there are 327 million people in the United States
Accessibility... for whom?
285 million people worldwide who are blind or visually impaired
Accessibility... for whom?
Accessibility... for whom?
275 million users who are deaf or hearing impaired
Accessibility... for whom?
Users with conditional disabilities
Accessibility... for whom?
Accessibility best practices benefit everyone.
I’m going to make the assumption that we all would like our applications to be accessible to all of these user groups, because we are an empathetic crew, and we care about our users.
🤗
What do we mean by "accessible"?
Web Content Accessibility Guidelines uses the acronym POUR
What do we mean by "accessible"?
- Perceivable
- Operable
- Understandable
- Robust
Perceivable
Examples:
- Alt text for images
- Content has sufficient contrast
- Captions for multimedia
Operable
Examples:
- Users can operate the app using a keyboard
- Users have enough time to read and use the content
Understandable
Examples:
- Text is readable
- Users understand how to correct mistakes
Robust
Examples:
- Content is robust across tools, including assistive technologies like screen readers
Why is accessibility difficult?
We agree our app should usable by a diverse range of users.
But... accessibility can feel like a daunting task.
Excellence in accessibility
Be very healthy
≈
Excellence in accessibility
Maybe you've made an accessibility goal?
My usual reasons
- 💬 I don’t know anything about accessibility!
- 💬 I don’t know how to check for accessibility!
- 💬 Adding accessibility takes too much time!
Maybe... I need a smaller goal.
Or... a tiny habit.
👩🚒 Tiny habits to the rescue!
Habits towards accessibility
🌳 Big Goal: Excellence in accessibility
Habits towards accessibility
🌱 Little Goal: I want to write UI tests based on interactions that would help someone using a screen reader navigate the page.
The month of accessible Elm!
Goal: testing for accessibility
- ✅ Purposeful
- ✅ Specific
- ✅ Time limited
🎉 TDD is an existing habit!
On to the project!
The project
One of the oldest pages on the site!
How do we verify this?
Best: verify with a screen reader user
How do we verify this?
Next best: verify myself with a screen reader, like Voice Over
How do we verify this?
Alright: let's just remove browser styling to see the most egregious errors
What will this reveal?
- Non-interactive elements with event listeners
- Places where we are using background images to convey content
What won't this reveal?
- Images with no alt text
- When we are using roles and widgets improperly
Okay, let's do this!
Selecting an interest
Selecting an interest
(Unstyled)
Deselecting interests
Deselecting interests
(Unstyled)
😱
Continuing onwards
Continuing onwards
(Unstyled)
On to the Elm!
Setting up a test
Understandable: Test for clear instructions on the page
avh4/elm-program-test
Text
spec : Test
spec =
test "instructs the user how to select interests" <|
\_ ->
ProgramTest.createDocument
{ init = Interests.init
, view = Interests.view
, update = Interests.update
}
|> ProgramTest.start testFlags
|> ProgramTest.ensureViewHas
[ Selector.text "Select your interests."
]
|> ProgramTest.done
Adding to our test
Operable: add a test for selecting an interest
Past habit: selecting by class name
Text
spec : Test
spec =
test "shows the interest page" <|
\_ ->
ProgramTest.createDocument
{ init = init
, view = view
, update = update
}
|> ProgramTest.start testFlags
|> ProgramTest.simulateDomEvent
(Query.find
[ Selector.class "InterestButtonClass"
]
)
Event.click
|> ProgramTest.done
Bad habit: selecting by classname
- Class names aren't helpful to screen reader users
- We might be clicking a non-interactive element
rtfeldman/elm-css
rtfeldman/elm-css
<div class="Interests-Page-InterestsClass">
Beyonce
</div>
viewInterest : String -> Html msg
viewInterest interest =
li
[ class [ InterestButtonClass ] ]
[ text interest ]
Enabling me to do...
spec : Test
spec =
test "shows the interest page" <|
\_ ->
ProgramTest.createDocument
{ init = init
, view = view
, update = update
}
|> ProgramTest.start testFlags
|> ProgramTest.simulateDomEvent
(Query.find
[ Selector.class "InterestButtonClass"
]
)
Event.click
|> ProgramTest.done
But then...
Elm CSS 13.0 arrived
Suddenly, styles lived inline!
<div class="_6206614">Beyonce</div>
viewInterest : String -> Html msg
viewInterest interest =
li
[ css
[ backgroundImage "so_interesting.gif"
]
]
[ text interest ]
Does this mean I can't write tests anymore?
So, I started doing something like this...
<div class="_6206614 good_ole_dependable_class">
Beyonce
</div>
viewInterest : String -> Html msg
viewInterest interest =
li
[ class "good_ole_dependable_class"
, css
[ backgroundImage "so_interesting.gif"
]
]
[ text interest ]
Wait... isn't there a better way to do this?
The test (before)
Text
spec : Test
spec =
test "shows the interest page" <|
\_ ->
ProgramTest.createDocument
{ init = init
, view = view
, update = update
}
|> ProgramTest.start testFlags
|> ProgramTest.simulateDomEvent
(Query.find
[ Selector.class "InterestButtonClass"
]
)
Event.click
|> ProgramTest.done
The test (after)
Text
spec : Test
spec =
test "shows the interest page" <|
\_ ->
ProgramTest.createDocument
{ init = init
, view = view
, update = update
}
|> ProgramTest.start testFlags
|> ProgramTest.clickButton "Select Beyonce"
|> ProgramTest.done
Wait so...
writing a test to enforce accessibility was easier than what I've been doing?
❌ We've got a failing test!
🕵️♀️ Original "Button"
selectInterestButton : Interest -> Html Msg
selectInterestButton interest =
li
[ onClick interest.onClick
, css
[ backgroundImage interest.image
]
]
[ text interest.name ]
li : List (Attribute msg) -> List (Html msg) -> Html msg
elm/html
(and styled html, too)
Already awesome, because we produce valid HTML markup by default but...
li : List (Attribute msg) -> List (Html msg) -> Html msg
elm/html
(and Html.Styled, too)
li : List (Attribute Never) -> List (Html msg) -> Html msg
tesk9/accessible-html
(and tesk9/accessible-html-with-css)
This argument is a list of type:
List (Html.Styled.Attribute Msg)
But `div` needs the 1st argument to be:
List (Html.Styled.Attribute Never)
import Accessibility.Styled exposing (..)
...
selectInterestButton : Interest -> Html Msg
selectInterestButton interest =
li
[ onClick interest.onClick
, css
[ backgroundImage interest.image
]
]
[ text interest.name ]
Make accessibility your compiler's habit.
import Accessibility.Styled exposing (..)
...
selectInterestButton : Interest -> Html Msg
selectInterestButton interest =
button
[ onClick interest.onClick
, css
[ backgroundImage interest.image
]
]
[ text ("Select " ++ interest.name) ]
✅ Our test passes!
It isn't all buttons
⚡️The power of invisibility
invisible : List (Attribute msg)
Makes content invisible without making it inaccessible.
⚡️The power of invisibility
import Accessibility.Styled.Style exposing (invisible)
...
deselectInterestButton : Interest -> Html Msg
deselectInterestButton interest =
button
[ onClick interest.onClick
, css
[ backgroundImage interest.image
]
]
[ span invisible
[ text ("Deselect " ++ interest.name)
]
]
Turns out... Elm is a great language for testing and implementing accessible features!
Elm nudges us towards good patterns.
These packages help extend Elm's capability
The final test flow
spec : Test
spec =
test "shows the interest page" <|
\_ ->
ProgramTest.createDocument
{ init = init
, view = view
, update = update
}
|> ProgramTest.start testFlags
|> ProgramTest.clickButton "Select Beyonce"
|> ProgramTest.clickButton "Deselect Beyonce"
...
|> ProgramTest.clickButton "Continue"
|> ProgramTest.done
👩🎨
The Results
Before (selecting interests)
After (selecting interests)
Before (deselecting interests)
After (deselecting interests)
Back to habits
- ✅ Tried
- ✅ Succeeded
- ✅ Kept after a month
Was that too small?
Brooke, all you did was add a few buttons and labels!
Was that too small?
Tiny habits that you keep are more impactful than sweeping resolutions that you give up.
❤️
I invite you to make an accessibility goal of your own this month!
💡 Ideas
Pick an item from the https://a11yproject.com/checklist/ and use it in your code
Switch to `tesk9/accessible-html` if your team is open to new Elm Packages
import Accessibility.Html as Html
Advocate for accessibility training at your workplace once a week
Download a browser extension like AXE and check every page you’re working on
Doing small things is better than doing no things!
Thank you!
Tell me about your accessibility habit
@brooke in the Elm Slack!
And a special thanks to...
- 📦 Tessa Kelly, Aaron Vonderhaar, and Richard Feldman for writing accessible Elm Packages
- 🗣 Matthew Griffith for all the great feedback
- Ꝕ Katie Hughes for being my project collaborator (that's the Squirrel emoji, apparently)
- 🎉 All of the awesome Elm Conf organizers!
A Month of Accessible Elm
By Brooke Angel
A Month of Accessible Elm
- 1,770