Upgrade to Pro — share decks privately, control downloads, hide ads and more …

アクセシブルなフロントエンド開発のこれまでとこれから / the past and future of accessible front-end development

Okuto Oyama
November 27, 2021

アクセシブルなフロントエンド開発のこれまでとこれから / the past and future of accessible front-end development

JSConfJP 2021 発表資料

昨今アクセシビリティに関する興味関心が増えてきた中で、SPAといったWebアプリケーションにおけるアクセシビリティを考慮した実装についてはまだ認識が広まっていないように感じます。 フロントエンドフレームワークを使ってより良いアクセシビリティ実装をするにはどうすればいいかの方法、その結果どういう形で伝わっているかを紹介しようと思います。 またWebアプリケーションでHTMLを使ってUI実装することに限界を感じており、それを解消するためのReactGUIやHeadless UIといった新たなアプローチにおける期待についても紹介したいと思っております。

Okuto Oyama

November 27, 2021
Tweet

More Decks by Okuto Oyama

Other Decks in Technology

Transcript

  1. ΞΫηγϒϧͳ


    ϑϩϯτΤϯυ։ൃͷ


    ͜Ε·Ͱͱ͜Ε͔Β
    େࢁԞਓɹ+4$POG+1 2021 - 2021/11/27

    View Slide

  2. ࣗݾ঺հ

    View Slide

  3. େࢁԞਓʢ͓͓΍·͓͘ͱʣ
    • he/him


    • גࣜձࣾΫϥ΢υϫʔΫε


    • ϓϩμΫτຊ෦ ϓϩμΫτ։ൃ෦

    ϓϥοτϑΥʔϜ։ൃ̏άϧʔϓ


    • ࣗশϑϩϯτΤϯυσβΠφʔ


    • ΞΫηγϏϦςΟܒ໤΍OSSίϯτϦ
    Ϗϡʔτ׆ಈΛ͍ͯ͠·͢


    • Ұࣇͷ෕

    View Slide

  4. ΠϯλʔωοτΞΠσϯςΟςΟɿyamanoku

    View Slide

  5. ຊ೔͓࿩͢Δ͜ͱ
    • ΞΫηγϏϦςΟͱ͸Կ͔


    • ͜Ε·ͰͷΞΫηγϏϦςΟରԠ


    • ͜Ε͔ΒͷΞΫηγϏϦςΟରԠ


    • ظ଴͍ͯ͠ΔΞϓϩʔν

    View Slide

  6. ̍.


    ΞΫηγϏϦςΟͱ͸Կ͔

    View Slide

  7. ΞΫηγϏϦςΟ

    View Slide

  8. ͋ΒΏΔਓ͕


    ΞΫηεͰ͖ΔΑ͏ʹ͢Δ

    View Slide

  9. ΢ΣϒΞΫηγϏϦςΟ

    View Slide

  10. ΢ΣϒΞΫηγϏϦςΟʛ֎຿ল
    “ϗʔϜϖʔδΛར༻͍ͯ͠Δશͯͷਓ͕ɺ
    ৺਎ͷ৚݅΍ར༻͢Δ؀ڥʹؔ܎ͳ͘ɺ
    ϗʔϜϖʔδͰఏڙ͞Ε͍ͯΔ৘ใ΍ػೳʹ
    ࢧোͳ͘ΞΫηε͠ɺར༻Ͱ͖Δ͜ͱ”

    View Slide

  11. Press Release: W3C Launches International Program Of
    fi
    ce for WAI
    “The power of the Web is in its universality. Access
    by everyone regardless of disability is an essential
    aspect.”

    View Slide

  12. ΢ΣϒʹΞΫηγϒϧ

    View Slide

  13. ଟ༷ͳϢʔβ૚Λߟྀ͢Δ

    View Slide

  14. ༷ʑͳίϯςΩετ͕ଘࡏ͢Δ
    • ૢ࡞Ͱ͖ΔσόΠε͕ෳ਺ଘࡏ͢Δ


    • αʔϏεΛ࢖ͬͯ΋Β͏ঢ়گɾ؀ڥ΋ҟͳΔ


    • ͳΜΒ͔ͷো֐͕͋Γૢ࡞͕༰қʹͰ͖ͳ͍৔߹΋͋Δ


    • ΞΫηγϏϦςΟΛҙࣝ͢Δͱߟྀ͢Δίετ΋ݮΒͤΔ

    View Slide

  15. ΞΫηεͰ͖Δ૚Λ૿΍͢

    View Slide

  16. ҰਓͻͱΓ͕Ͱ͖ΔΑ͏ʹͳΔ

    View Slide

  17. View Slide

  18. ࠜڌͷ͋Δ


    σβΠϯΛͭ͘Δ

    View Slide

  19. yamanoku.github.io/EXPLAINING_PORTFOLIO_SITE_ja.md
    “ϝΠϯίϯςϯπͷ࠷େ෯͸80chʹઃఆ͍ͯ͠·͢ɻch͸
    νΣʔϯͱݺ͹ΕɺจࣈͷαΠζʹΑͬͯՄม͢Δ୯ҐͰ
    ͢ɻ


    ͜ͷઃఆʹ͢Δ͜ͱͷϝϦοτͱͯ͠ɺ௕จ͕ಡΊͳ͍ಡࣈ
    ো֐ͷར༻ऀͷαϙʔτ͕Ͱ͖ͨΓɺจࣈαΠζ͕େ͖͘ͳ
    ΔʹैͬͯςΩετͷҰ෦͕͚ܽͯಡΊͳ͘ͳΔΑ͏ͳࣄଶ
    ΋ൃੜ͠ʹ͘͘ͳΓ·͢ɻ”

    View Slide

  20. ຽؒاۀ΁ͷ


    ߹ཧత഑ྀͷఏڙٛ຿

    View Slide

  21. ຽؒࣄۀऀʹ΋ো֐഑ྀٛ຿෇͚ɹվਖ਼ࠩผղফ๏੒ཱ ʛ ڞಉ௨৴
    “ো֐͕͋ΔਓͷҠಈ΍ҙࢥૄ௨Λແཧͷͳ
    ͍ൣғͰࢧԉ͢Δʮ߹ཧత഑ྀʯΛاۀ΍ళ
    ฮͳͲͷຽؒࣄۀऀʹٛ຿෇͚Δվਖ਼ো֐ऀ
    ࠩผղফ๏͕28೔ͷࢀӃຊձٞͰશձҰகͰ
    Մܾɺ੒ཱͨ͠ɻ”

    View Slide

  22. ো֐Λཧ༝ͱ͢Δࠩผͷղফͷਪਐʹؔ͢Δجຊํ਑ - ಺ֳ෎
    “๏͸ɺෆಛఆଟ਺ͷো֐ऀΛओͳର৅ͱͯ͠
    ߦΘΕΔࣄલతվળાஔʢதུʣʹ͍ͭͯ͸ɺ
    ݸผͷ৔໘ʹ͓͍ͯɺݸʑͷো֐ऀʹରͯ͠ߦ
    ΘΕΔ߹ཧత഑ྀΛత֬ʹߦ͏ͨΊͷ؀ڥͷ੔
    උͱ࣮ͯ͠ࢪʹ౒ΊΔ͜ͱͱ͍ͯ͠Δɻ”

    View Slide

  23. ΞΫηγϏϦςΟߟྀ͕


    ͞Ε͍ͯΔ͜ͱΛ஌Δ

    View Slide

  24. ΢ΣϒΞΫηγϏϦςΟࢼݧ݁Ռʢ2021೥౓ʣ

    ౦ژ౎ ৽ܕίϩφ΢Πϧεײછ঱ରࡦαΠτ

    View Slide

  25. ̎.


    ͜Ε·Ͱͷ


    ΞΫηγϏϦςΟରԠ

    View Slide

  26. ͜Ε·ͰͷΞΫηγϏϦςΟରԠ
    • ηϚϯςΟΫεϚʔΫΞοϓΛ஧࣮ʹ


    • HTMLΛਖ਼͘͠࢖͏


    • ෳࡶ͞͸ WAI-ARIA Ͱิࠤ͍ͯ͘͠

    View Slide

  27. ηϚϯςΟΫε


    ϚʔΫΞοϓ

    View Slide

  28. ηϚϯςΟΫεϚʔΫΞοϓ
    • ҙຯ࿦ͱͯ͠ͷදݱΛѻ͏Α͏ʹ͢Δ


    • ϚγϯϦʔμϒϧʹͳΔ


    • ࢧԉٕज़ʹ఻ΘΔΑ͏ʹม׵Ͱ͖Δ


    • ίϯςϯπҠಈ͢ΔͨΊͷΠϯλϑΣʔεʹ΋͋Δ


    • SEOͷ൑ఆʹ΋༏Ґੑ͕͋Δ

    View Slide

  29. HTML Λਖ਼͘͠࢖͏

    View Slide

  30. Use button, not div

    View Slide

  31. ͳͥ button Λ࢖༻͢Δͷ͔
    • ΢Σϒϒϥ΢β্ͰͷϢʔεέʔεΛදݱ͢Δ


    • UAσϑΥϧτελΠϧγʔτͰݟͨ໨͕ิ͑Δ


    • ΫϦοΫ΍ΩʔϘʔυૢ࡞


    • ϑΥʔΧε؅ཧ


    • ద੾ͳϚʔΫΞοϓͰ͸ͳ͍ͱϢʔεέʔεΛิ͑ͳ͍

    View Slide

  32. HTML Λਖ਼͘͠࢖͏ͨΊʹ
    • Nu Html Checker Λ௨ͯ͡ߏจΤϥʔͷ֬ೝΛ͢Δ


    • ೖΕࢠؔ܎Λཧղͯ͠࢖༻͢Δ


    • ࢧԉٕज़ʹ௨͡ΔϚʔΫΞοϓ


    • CSS͕ͳͯ͘΋ҙຯ͕ཧղͰ͖ΔΑ͏ʹͳ͍ͬͯΔ

    View Slide

  33. WAI-ARIA

    View Slide

  34. https://wicg.github.io/aom/explainer.html

    View Slide

  35. https://www.w3.org/TR/wai-aria-practices-1.1/examples/tabs/tabs-1/tabs.html

    View Slide

  36. WAI-ARIA Authoring Practices 1.1
    “No ARIA is better than Bad ARIA”

    View Slide

  37. ̏.


    ͜Ε͔Βͷ


    ΞΫηγϏϦςΟରԠ

    View Slide

  38. ͜Ε·ͰͷରԠ΋ܧଓ

    View Slide

  39. UI ͷ੍ޚ؅ཧ

    View Slide

  40. ϑΥʔΧεϚωδϝϯτ

    View Slide

  41. Tab Focus

    View Slide

  42. https://www.w3.org/TR/wai-aria-practices-1.1/examples/dialog-modal/dialog.html

    View Slide

  43. https://crowdworks.jp/login

    View Slide

  44. Sigle Page Application

    View Slide

  45. ONLY index.html

    View Slide

  46. ϑϩϯτΤϯυଆͰ


    ঢ়ଶΛ؅ཧ͢Δ

    View Slide

  47. ϖʔδʹ͋Δ


    ঢ়ଶΛมԽͤ͞Δ

    View Slide

  48. View Slide

  49. εΫϦʔϯϦʔμʔ


    Ͱ͸


    Ͳ͏ௌ͑͜Δͷ͔

    View Slide

  50. View Slide

  51. 🙄

    View Slide

  52. View Slide

  53. 🤔

    View Slide

  54. Ͳ͏ղܾ͢Ε͹͍͍͔ʁ

    View Slide

  55. ARIA ϥΠϒϦʔδϣϯ

    View Slide

  56. ֤ϑϨʔϜϫʔΫͷ


    ରԠΛݟͯΈΔ

    View Slide

  57. Next.js

    View Slide

  58. RouteAnnouncer
    const { asPath } = useRouter(
    )

    const [routeAnnouncement, setRouteAnnouncement] = React.useState(''
    )

    const initialPathLoaded = React.useRef(false
    )

    React.useEffect
    (

    () =>
    {

    if (!initialPathLoaded.current)
    {

    initialPathLoaded.current = tru
    e

    retur
    n

    }

    if (document.title)
    {

    setRouteAnnouncement(document.title
    )

    } else
    {

    const pageHeader = document.querySelector('h1'
    )

    const content = pageHeader?.innerText ?? pageHeader?.textConten
    t

    setRouteAnnouncement(content || asPath
    )

    }

    }
    ,

    [asPath
    ]

    )

    View Slide

  59. GatsbyJS

    View Slide

  60. SvelteKit

    View Slide

  61. svelte-announcer
    {#if mounted
    }

    >

    {#if navigated
    }

    {title
    }

    {/if
    }

    >

    {/if
    }

    View Slide

  62. Angular

    View Slide

  63. Async Pipe
    >

    >

    >

    View Slide

  64. LiveAnnouncer
    @Component(
    {

    selector: "app-component
    "

    providers: [LiveAnnouncer
    ]

    }
    )

    export class AppComponent
    {

    constructor(liveAnnouncer: LiveAnnouncer)
    {

    liveAnnouncer.announce("live region!")
    ;

    }

    }

    View Slide

  65. Vue.js

    View Slide

  66. vue-announcer
    {

    name: 'home'
    ,

    path: '/'
    ,

    component: Home
    ,

    meta:
    {

    announcer: { // vue-announcer setting
    s

    message: 'ϗʔϜը໘
    '

    }

    }

    }

    View Slide

  67. View Slide

  68. 👍

    View Slide

  69. ཹҙ͢Δ͜ͱ

    View Slide

  70. ಡΈ্͛Δ༻్Λཧղ͢Δ
    • ௨஌͸ૢ࡞ͷ౎౓ೖͬͯ͘ΔͱϢʔβମݧΛଛͶΔ


    • جຊతʹ͸ aria-live=“polite” Ͱ௨஌ͤ͞Δ


    • ݱࡏਐߦதͷಡΈ্͕͛׬ྃͰ͖͔ͯΒ௨஌͞ΕΔ


    • ۓٸੑͷߴ͍ϝοηʔδ͸ aria-live=“assertive” Ͱ௨஌ͤ͞Δ


    • role=“alert” ΋༗༻

    View Slide

  71. ̐.


    ظ଴͢ΔΞϓϩʔν

    View Slide

  72. https://docs.microsoft.com/ja-jp/lifecycle/faq/internet-explorer-microsoft-edge

    View Slide

  73. ϒϥ΢β͸


    υΩϡϝϯτϏϡϫʔ

    View Slide

  74. ϋΠύʔςΩετͱͯ͠ͷ HTML

    View Slide

  75. GUI ͱͯ͠ͷ HTMLʁ

    View Slide

  76. View Slide

  77. ࢲ͕ظ଴͢Δ΋ͷͷ঺հ

    View Slide

  78. React GUI

    View Slide

  79. React GUI
    • React DOM ޲͚ͷ GUI πʔϧΩοτ


    • υΩϡϝϯτϏϡϫʔ্ͰGUIΛදݱ͍ͤͨ͞


    • React Native for Web ͔Βͷܥේ


    • React Native ύʔπΛ࢖༻ͯ͠ Web ্Ͱදݱ͢Δ


    • RN for Web ͸Twitter Liteʢݱ Twitter WebʣͰ࢖༻

    View Slide

  80. σβΠϯγεςϜ


    ͔Βͷൃ૝

    View Slide

  81. Headless UI

    View Slide

  82. Headless UI
    • Tailwind Labs ։ൃ


    • ݟͨ໨Λ࡞͍ͬͯͳ͍ UI ϥΠϒϥϦ


    • Tailwind CSS ͳͲͰఆٛͰ͖Δ


    • React.js ͱ Vue3 ͷΈରԠ

    View Slide

  83. React Aria

    View Slide

  84. React Aria
    • Adobe ։ൃ


    • React Spectrum σβΠϯγεςϜΛߏ੒͢Δ̍ཁૉ


    • UIͱͯ͠ͷڍಈɺΞΫηγϏϦςΟ࣮૷Λ Hooks Ͱ෼཭


    • React.js ͷΈରԠ

    View Slide

  85. JavaScript ʹ


    ৼΔ෣͍Λू໿ͤ͞Δ

    View Slide

  86. React A11y & TailwindCSS
    let { buttonProps, isPressed } = useButton(props, ref)
    ;

    let { focusProps, isFocusVisible } = useFocusRing()
    ;

    let className = classNames
    (

    props.isDisabled ? "bg-gray-400" : isPressed ? "bg-blue-700" : "bg-blue-500"
    ,

    "text-white", "font-bold"
    ,

    "py-2", "px-4"
    ,

    "rounded"
    ,

    "cursor-default"
    ,

    "focus:outline-none"
    ,

    isFocusVisible ? "shadow-outline" : ""
    ,

    "transition", "ease-in-out", "duration-150
    "

    );

    View Slide

  87. useNumberField
    function NumberField(props)
    {

    let {locale} = useLocale()
    ;

    let state = useNumberFieldState({...props, locale})
    ;

    let inputRef = React.useRef()
    ;

    let incrRef = React.useRef()
    ;

    let decRef = React.useRef()
    ;

    let { labelProps, groupProps, inputProps, incrementButtonProps, decrementButtonProps } = useNumberField(props, state, inputRef)
    ;

    let {buttonProps: incrementProps} = useButton(incrementButtonProps, incrRef)
    ;

    let {buttonProps: decrementProps} = useButton(decrementButtonProps, decRef)
    ;

    return
    (

    >

    {props.label}>

    >

    >

    -

    >

    >

    >

    +

    >

    >

    >

    )
    ;

    }

    View Slide

  88. ৽ͨͳΔ


    ϢʔεέʔεΛ


    ૑଄͢Δ

    View Slide

  89. Custom Elements

    View Slide


  90. View Slide

  91. Custom Elements Λੜ੒͢Δ
    class ToggleButton extends HTMLElement {

    connectedCallback()
    {

    this.setAttribute("role", "button")
    ;

    this.setAttribute("aria-pressed", “false")
    ;

    this.addEventListener("click", togglePressed)
    ;

    this.addEventListener("keydown", function (event)
    {

    if (event.key === "Enter" || event.key === "Space")
    {

    togglePressed()
    ;

    }

    })
    ;

    }

    }

    customElements.define("toggle-button", ToggleButton);

    View Slide

  92. DOMͰදࣔ͞ΕΔͱ͖͢΂ͯݱΕΔ
    >

    Toggl
    e

    >

    >

    Default Butto
    n


    View Slide

  93. Accessibility Object Model

    View Slide

  94. Custom Element & AOM Ͱදݱ͢Δ
    class ToggleButton extends HTMLElement
    {

    constructor()
    {

    super()
    ;

    this._internals = this.attachInternals()
    ;

    this._internals.role = "button";

    this._internals.ariaPressed = false
    ;

    }

    }

    ToggleButton.addEventListener('keydown', (event) =>
    {

    if (event.key === "Enter" || event.key === "Space")
    {

    toggleButton()
    ;

    }

    })
    ;

    customElements.define("toggle-button", ToggleButton);

    View Slide

  95. ͷΑ͏ʹӅṭ͞ΕΔ
    >

    ɹToggl
    e

    >

    >

    Default Butto
    n


    View Slide

  96. ࣗΒͰఆ͍ٛͯ͘͠

    View Slide

  97. ੍໿͕͋ΔͨΊ


    ۜͷ஄ؙͰ͸ͳ͍

    View Slide

  98. ࠓޙ΋ಈ޲Λ௥͍ͭͭ

    View Slide

  99. ঢ়گ΍


    Ϣʔεέʔεʹ߹Θͤͯ


    ׆༻Ͱ͖ΔΑ͏ʹ

    View Slide

  100. ͓ΘΓʹ

    View Slide

  101. ΞΫηγϏϦςΟରԠ

    View Slide

  102. ΢ΣϒΞΫηγϏϦςΟͷ̐ݪଇ

    View Slide

  103. ΢ΣϒΞΫηγϏϦςΟͷ̐ݪଇ
    1. ஌֮Մೳ


    2. ૢ࡞Մೳ


    3. ཧղՄೳ


    4. ݎ࿚

    View Slide

  104. ΢ΣϒΞΫηγϏϦςΟͷ̐ݪଇ
    1. ஌֮Մೳ


    2. ૢ࡞Մೳ


    3. ཧղՄೳ


    4. ݎ࿚

    View Slide

  105. ஌֮Ͱ͖ΔΑ͏ʹ͢Δ

    View Slide

  106. ৮ΕΔ΋ͷΛͭ͘Δ

    View Slide

  107. ৮ΕΔͱ͍͏͜ͱ͸


    ͦ͜ʹԿ͔͕͋Δͱ


    ؾ෇͚Δ

    View Slide

  108. ख͕͔ΓΛ࡞Δ͜ͱ͕


    ΞΫηγϏϦςΟ


    ରԠͷҰาʹͳΔ

    View Slide

  109. ͔͔̌̍ͷ࿩͡Όͳ͍

    View Slide

  110. ৮ΕΔ΋ͷΛ


    ࡞͍ͬͯ͜͏

    View Slide

  111. ͋ΒΏΔਓ͕


    ࢀՃͰ͖ΔΑ͏ʹ


    ͳΔͨΊʹ

    View Slide

  112. ΞΫηγϒϧͳ


    ϑϩϯτΤϯυ։ൃΛ


    ΍͍ͬͯ͜͏

    View Slide

  113. ͜Ε·Ͱ΋


    ͦͯ͠


    ͜Ε͔Β΋

    View Slide

  114. ΞΫηγϒϧͳ


    ϑϩϯτΤϯυ։ൃͷ


    ͜Ε·Ͱͱ͜Ε͔Β
    େࢁԞਓɹ+4$POG+1 2021 - 2021/11/27

    View Slide

  115. ͋Γ͕ͱ͏͍͟͝·ͨ͠

    View Slide