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 full-size slide

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


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


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

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


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


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


    • Ұࣇͷ෕

    View full-size slide

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

    View full-size slide

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


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


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


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

    View full-size slide

  5. ̍.


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

    View full-size slide

  6. ΞΫηγϏϦςΟ

    View full-size slide

  7. ͋ΒΏΔਓ͕


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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  10. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

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


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


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


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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  16. ࠜڌͷ͋Δ


    σβΠϯΛͭ͘Δ

    View full-size slide

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


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

    View full-size slide

  18. ຽؒاۀ΁ͷ


    ߹ཧత഑ྀͷఏڙٛ຿

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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


    ͞Ε͍ͯΔ͜ͱΛ஌Δ

    View full-size slide

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

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

    View full-size slide

  23. ̎.


    ͜Ε·Ͱͷ


    ΞΫηγϏϦςΟରԠ

    View full-size slide

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


    • HTMLΛਖ਼͘͠࢖͏


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

    View full-size slide

  25. ηϚϯςΟΫε


    ϚʔΫΞοϓ

    View full-size slide

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


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


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


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


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

    View full-size slide

  27. HTML Λਖ਼͘͠࢖͏

    View full-size slide

  28. Use button, not div

    View full-size slide

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


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


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


    • ϑΥʔΧε؅ཧ


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

    View full-size slide

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


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


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


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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  34. ̏.


    ͜Ε͔Βͷ


    ΞΫηγϏϦςΟରԠ

    View full-size slide

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

    View full-size slide

  36. UI ͷ੍ޚ؅ཧ

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  39. https://crowdworks.jp/login

    View full-size slide

  40. Sigle Page Application

    View full-size slide

  41. ONLY index.html

    View full-size slide

  42. ϑϩϯτΤϯυଆͰ


    ঢ়ଶΛ؅ཧ͢Δ

    View full-size slide

  43. ϖʔδʹ͋Δ


    ঢ়ଶΛมԽͤ͞Δ

    View full-size slide

  44. εΫϦʔϯϦʔμʔ


    Ͱ͸


    Ͳ͏ௌ͑͜Δͷ͔

    View full-size slide

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

    View full-size slide

  46. ARIA ϥΠϒϦʔδϣϯ

    View full-size slide

  47. ֤ϑϨʔϜϫʔΫͷ


    ରԠΛݟͯΈΔ

    View full-size slide

  48. 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 full-size slide

  49. svelte-announcer
    {#if mounted
    }

    >

    {#if navigated
    }

    {title
    }

    {/if
    }

    >

    {/if
    }

    View full-size slide

  50. Async Pipe
    >

    >

    >

    View full-size slide

  51. LiveAnnouncer
    @Component(
    {

    selector: "app-component
    "

    providers: [LiveAnnouncer
    ]

    }
    )

    export class AppComponent
    {

    constructor(liveAnnouncer: LiveAnnouncer)
    {

    liveAnnouncer.announce("live region!")
    ;

    }

    }

    View full-size slide

  52. vue-announcer
    {

    name: 'home'
    ,

    path: '/'
    ,

    component: Home
    ,

    meta:
    {

    announcer: { // vue-announcer setting
    s

    message: 'ϗʔϜը໘
    '

    }

    }

    }

    View full-size slide

  53. ཹҙ͢Δ͜ͱ

    View full-size slide

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


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


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


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


    • role=“alert” ΋༗༻

    View full-size slide

  55. ̐.


    ظ଴͢ΔΞϓϩʔν

    View full-size slide

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

    View full-size slide

  57. ϒϥ΢β͸


    υΩϡϝϯτϏϡϫʔ

    View full-size slide

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

    View full-size slide

  59. GUI ͱͯ͠ͷ HTMLʁ

    View full-size slide

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

    View full-size slide

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


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


    • React Native for Web ͔Βͷܥේ


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


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

    View full-size slide

  62. σβΠϯγεςϜ


    ͔Βͷൃ૝

    View full-size slide

  63. Headless UI
    • Tailwind Labs ։ൃ


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


    • Tailwind CSS ͳͲͰఆٛͰ͖Δ


    • React.js ͱ Vue3 ͷΈରԠ

    View full-size slide

  64. React Aria
    • Adobe ։ൃ


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


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


    • React.js ͷΈରԠ

    View full-size slide

  65. JavaScript ʹ


    ৼΔ෣͍Λू໿ͤ͞Δ

    View full-size slide

  66. 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 full-size slide

  67. 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 full-size slide

  68. ৽ͨͳΔ


    ϢʔεέʔεΛ


    ૑଄͢Δ

    View full-size slide

  69. Custom Elements

    View full-size slide

  70. 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 full-size slide

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

    Toggl
    e

    >

    >

    Default Butto
    n


    View full-size slide

  72. Accessibility Object Model

    View full-size slide

  73. 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 full-size slide

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

    ɹToggl
    e

    >

    >

    Default Butto
    n


    View full-size slide

  75. ࣗΒͰఆ͍ٛͯ͘͠

    View full-size slide

  76. ੍໿͕͋ΔͨΊ


    ۜͷ஄ؙͰ͸ͳ͍

    View full-size slide

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

    View full-size slide

  78. ঢ়گ΍


    Ϣʔεέʔεʹ߹Θͤͯ


    ׆༻Ͱ͖ΔΑ͏ʹ

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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


    2. ૢ࡞Մೳ


    3. ཧղՄೳ


    4. ݎ࿚

    View full-size slide

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


    2. ૢ࡞Մೳ


    3. ཧղՄೳ


    4. ݎ࿚

    View full-size slide

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

    View full-size slide

  84. ৮ΕΔ΋ͷΛͭ͘Δ

    View full-size slide

  85. ৮ΕΔͱ͍͏͜ͱ͸


    ͦ͜ʹԿ͔͕͋Δͱ


    ؾ෇͚Δ

    View full-size slide

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


    ΞΫηγϏϦςΟ


    ରԠͷҰาʹͳΔ

    View full-size slide

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

    View full-size slide

  88. ৮ΕΔ΋ͷΛ


    ࡞͍ͬͯ͜͏

    View full-size slide

  89. ͋ΒΏΔਓ͕


    ࢀՃͰ͖ΔΑ͏ʹ


    ͳΔͨΊʹ

    View full-size slide

  90. ΞΫηγϒϧͳ


    ϑϩϯτΤϯυ։ൃΛ


    ΍͍ͬͯ͜͏

    View full-size slide

  91. ͜Ε·Ͱ΋


    ͦͯ͠


    ͜Ε͔Β΋

    View full-size slide

  92. ΞΫηγϒϧͳ


    ϑϩϯτΤϯυ։ൃͷ


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

    View full-size slide

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

    View full-size slide