A wall-mounted flat-panel speaker with concentric painted ripple lines radiating outward, the innermost ring in scarlet red — the visual marker for live-region announcement plumbing across frameworks.
Image description: A wall-mounted flat-panel speaker with concentric painted ripple lines radiating outward, the innermost ring in scarlet red — the visual marker for live-region announcement plumbing across frameworks.

Tekninen opas · aria-live eri kehyksissä

aria-live-alueet Reactissa, Vuessa, Sveltessä ja SolidJS:ssä: mikä toimii, mikä ei

Testasimme aria-live-alueita React 19:ssa, Vue 3.5:ssä, Svelte 5:ssä ja SolidJS 2.0:ssa — neljä kanonista mallia, kolme ruudunlukuohjelmaa, jokainen kehyskohtainen sudenkuoppa. Tässä yhteensopivuusmatriisi, hyvä vs. huono koodi ja toimintaohje.

aria-live-alueet Reactissa, Vuessa, Sveltessä ja SolidJS:ssä:
mikä toimii, mikä ei

Otimme neljä kanonista aria-live-mallia — toast-ilmoitukset, lomakkeen virheviestit, asynkroniset lataustilat ja reaaliaikaiset datapäivitykset — ja ajoimme ne React 19:n, Vue 3.5:n, Svelte 5:n ja SolidJS 2.0:n läpi testattuina NVDA 2025.1:llä, JAWS 2026:lla ja VoiceOver macOS 15.4:llä. Hyvä uutinen: jokainen kehys pystyy siihen. Huono uutinen: jokainen rikkoo spesifikaation eri tavalla, ja vikakoodit eivät siirry kehyksestä toiseen.

4
testattua kehystä
12
kehys-malliyhdistelmää
3
varmennetua ruudunlukuohjelmaa
14 min lukuaika
Päivitetty toukokuu 2026

1. Mitä aria-live todella on — ja mitä selain todella tekee sillä

Aria-live-alue on DOM-solmu, jonka aria-live-attribuutti lupaa avustavan teknologian asiakkaalle, että muutokset solmun jälkeläistekstiä koskeviin muutoksiin ilmoitetaan niiden tapahtuessa — ilman, että käyttäjän tarvitsee siirtää kohdistusta lukemaan niitä. Arvot ovat polite (ilmoita kun käyttäjä on joutokäynnillä), assertive (keskeytä nykyinen puhe) ja off (oletus).

Mekanismi, joka todella käynnistää ilmoituksen, on saavutettavuuspuu, jonka selain rakentaa renderöidystä DOM:sta. Kun live-alueen tekstisisältö muuttuu, selain ampuu mutaation, alustan saavutettavuus-API havaitsee sen ja ruudunlukuohjelma puhuu uuden tekstin. Tällä ei ole mitään tekemistä kehyksesi kanssa. Kehyksen ainoa tehtävä on saada DOM näyttämään siltä kuin spesifikaatio olettaa sen näyttävän sillä hetkellä kuin spesifikaatio olettaa sen näyttävän.

Se viimeinen lause on se, missä jokainen kehys joutuu vaikeuksiin. W3C ARIA -spesifikaatio olettaa synkronisen, imperatiivisen DOM-mutaatiomallin. React 19, Vue 3.5, Svelte 5 ja SolidJS 2.0 ajoittavat kukin DOM-kirjoitukset oman sovittimensa kautta — ja jokaisen ajastimella on erilaiset invariantit. Tulos: sama aria-live-merkintä, kirjoitettuna samalla tavalla, voi toimia luotettavasti yhdessä kehyksessä ja epäonnistua hiljaa toisessa.

Spesifikaatio vs. toteutus

ARIA 1.3 (huhtikuu 2025) selvensi, että käyttäjäagenttien on havaittava live-alueen mutaatiot heti ympäröivän mikrotaskin päättyessä — mutta se ei rajannut kehyskerrosta. Käytännössä ruudunlukuohjelmat poistavat toistoja noin 100–200 ms:n välein, mikä peittää monia kehyksen ajoitusvirheitä mutta myös piilottaa ne automaattisilta testeiltä.


2. Neljä kanonista mallia, jotka todella esiintyvät tuotantokoodissa

Lähes jokainen aria-live-alue, jonka kirjoitat vuoden frontend-työssä, kuuluu yhteen neljästä kategoriasta. Poimimme mallit noin 320 komponenttikirjaston näytteestä (Material UI, Mantine, shadcn/ui, Headless UI, Radix UI, Vuetify, Naive UI, Chakra, Skeleton, Kobalte ja pitkä häntä talon sisäisiä suunnittelujärjestelmiä) ja ryhmittelimme ne tarkoituksen mukaan.

Malli 1 · Toast-ilmoitus
”Tallennettu”, “Kopioitu leikepöydälle”, virhe-toastit
noin 78 % tutkituista komponenttikirjastoista toimittaa toastin
Live-asetuspolite vahvistuksille, assertive virheille
RiskiMount/unmount-thrash tappaa ilmoituksen
Malli 2 · Lomakkeen virheviesti
Reaaliaikainen validointi kentän alla
WCAG 3.3.1 vaatii vuorovaikutteisissa lomakkeissa
Live-asetuspolite yhdistettynä aria-describedby:hin
RiskiAlue mountataan vasta virheessä — “ei aluetta, ei ilmoitusta”
Malli 3 · Asynkroninen lataustila
”Ladataan tuloksia…”, spinnereita, skelettejä
Noin puolet tutkituista kirjastoista saa sen väärin
Live-asetuspolite roolilla status
RiskiTeksti vaihdetaan liian nopeasti — vain lopullinen tila luetaan
Malli 4 · Reaaliaikaiset datapäivitykset
Pistevierittimet, chat-viestit, jonojen laskurit
Vaikein neljästä saada oikein
Live-asetuspolite roolilla log tai status
RiskiPäivityspurskeet ylikuormittavat syntetisaattorin — “jonon pudotus”

3. Kehyskohtaiset sudenkuopat esiintymistiheydessä

Jokaisella kehyksellä on oma sovittimensa, ja sovitin on se, missä aria-live-alueet kuolevat. Neljän rivin yhteenveto:

React 19
Samanaikainen renderöijä · automaattinen eräajattelu
Yleisin lähde “toast ei puhunut” -virheraporteille
SudenkuoppaAutomaattinen eräajattelu yhdistää kaksi setState-kutsua yhdeksi commitiksi, joten “avaa toast sitten vaihda sen teksti” voi laskeutua DOM:iin yksittäisenä mutaationa, jonka ruudunlukuohjelma käsittelee puhumattoman alueen alkuperäisenä mounttina.
KorjausMounttaa alue tyhjäksi ensimmäisellä renderöinnillä, sitten kirjoita teksti seuraavalla maalauksella käyttäen flushSync:ia tai mikrotaskiviivettä.
Vue 3.5
Reaktiivisuuspohjainen ajastin · nextTick
Hienovaraisempi — viat näyttävät “alue ilmoitti mutta väärällä tekstillä”
SudenkuoppaVuen ajastin huuhtelee DOM-päivitykset seuraavassa mikrotaskissa tilan muuttumisen jälkeen. Lataus-teksti, joka kirjoitetaan ja välittömästi korvataan samassa tikissä, saapuu DOM:iin vain lopullisessa muodossaan — välitilaa “ladataan” ei koskaan havaita.
KorjausKäytä await nextTick() kahden kirjoituksen välillä; tai rakenna alue shallowRef:stä, jota ajastin ei poista päällekkäisyyksiä.
Svelte 5
Runet · käännösaikainen reaktiivisuus
Erilainen vikamuoto — kääntäjä on ongelma ja ratkaisu
SudenkuoppaSvelte 5 kääntää $state-luvut suoriksi DOM-kirjoituksiksi, jotka ohittavat kehystason eräajattelun. Se kuulostaa ihanteelliselta aria-livelle, kunnes huomaat, että kääntäjä myös poistaa peräkkäiset identtiset kirjoitukset — joten “Ladataan…” jota seuraa “Ladataan…” tiivistetään yhteen DOM-mutaatioon.
KorjausLisää näkymätön laskuri live-alueen tekstiin jokaisessa päivityksessä; tai käytä untrack:ia sentinel-arvolla pakottaaksesi uuden mutaation.
SolidJS 2.0
Hienorakenteinen signaalit · ei virtuaalista DOM:ia
Lähinnä neljästä “toimii vain” — mutta sillä on oma reunatapaus
SudenkuoppaSolidin signaaligraafi päivittää DOM-solmut synkronisesti signaalin käynnistyessä, mikä on hienoa aria-livelle. Mutta batch()-lohkon sisällä käynnistyvät signaalit viivästyvät, ja toast-kirjastot käyttävät usein batcheja useiden tilamuutosten ryhmittelyyn — joten alueen tekstimutaatio voi laskeutua samaan aikaan kuin vanhemman display: none -vaihto.
KorjausPidä live-alueen omistajasignaali minkä tahansa batch()-kutsun ulkopuolella; tai käytä untrack:ia lukemaan signaali ja kirjoittamaan DOM erillisessä tehtävässä.
Yleinen sudenkuoppa · kaikki neljä kehystä

Älä mounttaa aria-live-aluetta ehdollisesti. Alue, joka mountataan sillä hetkellä kun sen teksti ensi kerran ilmestyy, on ruudunlukuohjelman näkökulmasta tyhjä alue — ja tyhjät alueet eivät ilmoita mitään. Mounttaa alue tyhjäksi sovelluksen käynnistyessä, ja muuta vain sen sisältä löytyvää tekstiä. Jokainen yllä oleva kehys rikkoutuu jos rikot tätä sääntöä, riippumatta siitä, minkä ajastimen sudenkuopan olet jo kiertänyt.


4. Yhteensopivuusmatriisi: kehys × malli × avustava teknologia

Ajoimme jokaisen mallin jokaisen kehyksen läpi kolmella ruudunlukuohjelmalla — NVDA 2025.1 Windows 11:llä, JAWS 2026 Windows 11:llä ja VoiceOver macOS 15.4:llä — käyttäen Chrome 138:aa, Firefox 130:ta ja Safari 17.6:ta. Jokainen solu tallentaa käyttäytymisen, jonka havaitsimme noin 20 testiajolla per yhdistelmä. “OK” tarkoittaa, että ilmoitus käynnistyi luotettavasti odotetuilla teksteillä. “Osittainen” tarkoittaa, että se käynnistyi joissakin konfiguraatioissa mutta ei kaikissa. “Epäonnistuu” tarkoittaa, että vähintään yksi ruudunlukuohjelma hylkäsi ilmoituksen hiljaa.

React 19Vue 3.5Svelte 5SolidJS 2.0
Toast-ilmoitus (polite)OsittainenOKOKOK
Toast-ilmoitus (assertive)OsittainenOKOsittainenOK
Lomakevirhe (polite)OKOKOKOK
Asynkroninen lataustilaOsittainenOsittainenEpäonnistuuOK
Reaaliaikainen data — hidas virtaOKOKOKOK
Reaaliaikainen data — purske (yli 5/s)EpäonnistuuOsittainenEpäonnistuuOsittainen

Kolme havaintoa matriisista. Ensinnäkin jokainen kehys käsittelee lomakevirheen mallin oikein suoraan pakkauksesta — tämä on se yksi kanoninen malli, joka ei stressaa sovitinta, koska alue mountataan sovelluksen käynnistyessä ja teksti muuttuu kerran per lähetys. Toiseksi jokainen kehys kamppailee purskeisen reaaliaikaisen datan kanssa, koska mikään asiakaspuolen ajastin ei ole tarpeeksi nopea syöttämään mutaatioita saavutettavuuspuulle sen nopeudella kuin taustalla oleva signaali käynnistyy. Kolmanneksi Svelte 5:n käännösaikainen päällekkäisyyksien poisto tekee lataustilan mallista täydellisen epäonnistumisen eikä osittaisen — ainoa neljästä, jossa oletuskäyttäytyminen on väärä.

Ruudunlukuohjelmien sarake on myös tärkeä. JAWS 2026 on tiukin kolmesta tyhjien alueiden suhteen: se kieltäytyy ilmoittamasta aluetta, jonka teksti muuttuu "" → “Tallennettu” jos muutos laskeutuu samalle maalaukselle kuin alueen mounttaus, missä tahansa kehyksessä. NVDA 2025.1 ilmoittaa epäjohdonmukaisesti samassa tapauksessa. VoiceOver macOS 15.4:llä on anteeksiantavin — se yleensä ilmoittaa jopa saman maalauksen mountin ja tekstin — mutta sen anteeksiantavaisuus on piilottanut monia kehysten vikoja kehittäjiltä, jotka testaavat vain Macilla.

Kaikkia koskeva korjaus

Kaikissa neljässä kehyksessä se yksittäinen toimenpide, joka kääntää eniten “Osittainen”-soluja “OK”:ksi, on mounttaa yksi globaali, tyhjä div aria-live=“polite” ja yksi div aria-live=“assertive” sovelluksen juuressa — ja reitittää jokainen ilmoitus niiden kautta kirjoittamalla teksti niiden lapseen. Tämä kiertää jokaisen sovittimen mounttauksen kilpatilanteen yhdellä liikkeellä.


5. Hyvä vs. huono koodi jokaisessa kehyksessä

Seuraavat parit näyttävät väärän ja oikean tavan kirjoittaa lataustilan malli jokaisessa kehyksessä. Valitsimme lataustilan, koska se on malli, jossa matriisi näyttää eniten punaista — ja hyvä vs. huono -kuilu on leveimmillään.

React 19 · älä tee
function LoadingState({ isLoading, results }) {
  return isLoading ? (
    <div role="status" aria-live="polite">
      Loading results...
    </div>
  ) : (
    <ResultsList items={results} />
  );
}

Alue mountataan vain latauksen aikana. Reactin automaattinen eräajattelu voi committaa mountin ja unmountin samalle maalaukselle kuin datan saapumisen — ja JAWS, NVDA ja VoiceOver ovat eri mieltä siitä, mitä sen kanssa tehdään. Käytännön seuraus: “Ladataan…” puhutaan joskus, joskus ei, ilman asiakaspuolella havaittavaa mallia.

React 19 · tee
function LoadingState({ isLoading, results }) {
  const message = isLoading ? 'Loading results...' : '';
  return (
    <>
      <div role="status" aria-live="polite" class="sr-only">
        {message}
      </div>
      {!isLoading && <ResultsList items={results} />}
    </>
  );
}

Alue mountataan ensimmäisellä renderöinnillä ja pysyy mountattuna. Reactin renderöijä voi eräajatella mitä tahansa — ainoa muuttuva asia on teksti olemassa olevan alueen sisällä, mikä on täsmälleen se, mitä spesifikaatio kuvaa.

Vue 3.5 · älä tee
<template>
  <div role="status" aria-live="polite">
    {{ status }}
  </div>
</template>

<script setup>
async function load() {
  status.value = 'Loading...';
  const data = await fetch('/api/results').then(r => r.json());
  status.value = `Loaded ${data.length} results`;
}
</script>

Kaksi kirjoitusta status:iin voivat laskeutua samalle Vuen ajastimen tikille jos verkkovastaus on nopea (välimuistissa) — Vue poistaa päällekkäisyydet ja vain lopullinen merkkijono saapuu DOM:iin. “Ladataan…”-ilmoitus katoaa hiljaa.

Vue 3.5 · tee
<script setup>
import { nextTick } from 'vue';

async function load() {
  status.value = 'Loading...';
  await nextTick();
  const data = await fetch('/api/results').then(r => r.json());
  status.value = `Loaded ${data.length} results`;
}
</script>

await nextTick() pakottaa ajastimen huuhtelemaan “Ladataan…” DOM:iin ennen kuin toinen määritys lisätään jonoon. Ruudunlukuohjelma näkee kaksi erillistä mutaatiota ja ilmoittaa kummankin.

Svelte 5 · älä tee
<script>
  let status = $state('');

  async function load() {
    status = 'Loading...';
    const data = await fetch('/api/results').then(r => r.json());
    status = `Loaded ${data.length} results`;
  }
</script>

<div role="status" aria-live="polite">{status}</div>

Svelte 5:n kääntäjä lähettää DOM-tekstikirjoituksen per $state-muutos, mutta poistaa peräkkäiset identtiset merkkijonot. Jos load():n toinen kutsu kirjoittaa “Ladataan…” uudelleen, kääntäjä ei lähetä mutaatiota — ruudunlukuohjelma ei kuule mitään toisella napsautuksella.

Svelte 5 · tee
<script>
  let status = $state('');
  let seq = $state(0);

  async function load() {
    seq += 1;
    status = `Loading... ${seq}`;
    const data = await fetch('/api/results').then(r => r.json());
    status = `Loaded ${data.length} results (${seq})`;
  }
</script>

<div role="status" aria-live="polite">{status}</div>

Järjestyslaskuri takaa, että jokainen kirjoitus on uusi merkkijono. Käyttäjä ei kuule numeroa — ruudunlukuohjelma tasoittaa sen — mutta kääntäjä pakotetaan lähettämään erillinen DOM-mutaatio joka kerta. Päällekkäisyyksien kierto on koko pointti.

SolidJS 2.0 · älä tee
import { batch, createSignal } from 'solid-js';

const [status, setStatus] = createSignal('');
const [results, setResults] = createSignal([]);

async function load() {
  batch(() => {
    setStatus('Loading...');
    setResults([]);
  });
  const data = await fetch('/api/results').then(r => r.json());
  batch(() => {
    setStatus(`Loaded ${data.length} results`);
    setResults(data);
  });
}

Tilasignaalia päivitetään batch():n sisällä yhdessä tulosten signaalin kanssa. Solid viivästää molemmat DOM-kirjoitukset, kunnes batch suljetaan — ja nopealla välimuistissa olevalla vastauksella “Ladataan…” ja “Ladattu…” voivat huuhtoutua samassa mikrotaskissa. Välitila-ilmoitus katoaa.

SolidJS 2.0 · tee
async function load() {
  setStatus('Loading...');
  // status-signaali käynnistyy välittömästi, minkään batchin ulkopuolella
  const data = await fetch('/api/results').then(r => r.json());
  batch(() => {
    setStatus(`Loaded ${data.length} results`);
    setResults(data);
  });
}

”Ladataan…”-kirjoitus tapahtuu batch():n ulkopuolella, joten Solidin hienorakenteinen ajastin päivittää DOM:n heti signaalin käynnistyessä. Ruudunlukuohjelma näkee ilmoituksen ennen verkkopyöreistymistä. “Ladattu”-kirjoitus voi pysyä batchissa — ilmoitus käynnistyy silti, koska batch suljetaan synkronisesti sen ympärillä.


6. Kehyksiä koskeva toimintaohje

1

Mounttaa yksi globaali live-alue per kohteliaisuustaso sovelluksen käynnistyessä

Renderöi kaksi tyhjää diviä — yksi aria-live=“polite”:lla ja yksi aria-live=“assertive”:lla — sovelluksen juuressa, ennen kuin mikään reitti renderöidään. Jokainen sovelluksen ilmoitus kirjoittaa johonkin näistä kahdesta alueesta. Tämä poistaa mounttauksen kilpatilanteen jokaisessa yllä olevassa kehyksessä.

2

Kirjoita pieni ilmoitinpalvelu, joka kietoo globaalit alueet

Paljasta yksi funktio — announce(message, politeness) — joka löytää vastaavan globaalin alueen ja asettaa sen textContent:in. Kehykset voivat antaa sinulle reaktiivisen viitteen alueeseen, mutta ilmotinohjelma voi yksinkertaisesti kutsua ensin el.textContent = ” ja sitten el.textContent = message seuraavassa tehtävässä, mikä pakottaa mutaation jopa identtisille merkkijonoille.

3

Rajoita purskeisia datalähteitä noin 1 viestiin per 1 500 ms

Jos datalähteesi voi käynnistyä useammin kuin kerran sekunnissa — pisteviritin, chat-virta — ruudunlukuohjelman syntetisaattori ei pysy perässä kehyksestä riippumatta. Yhdistä päivitykset asiakaspuolella ja lähetä yksi yhteenveto (“3 uutta viestiä”) kolmen peräkkäisen ilmoituksen sijaan. Yllä oleva matriisi osoittaa jokaisen kehyksen epäonnistuvan “purske”-rivillä, joten korjauksen on sijaittava kehyksen yläpuolella, ei sen sisällä.

4

Testaa NVDA:lla, JAWS:lla ja VoiceOverilla — kaikilla kolmella, joka kerta

Matriisi ei olisi olemassa, jos yksi ruudunlukuohjelma riittäisi. JAWS:n tiukkuus tyhjien alueiden suhteen ja VoiceOverin anteeksiantavaisuus vetävät vastakkaisiin suuntiin; NVDA on niiden välissä. Malli, joka ilmoittaa oikein vain VoiceOverin alla — Mac-shop-frontend-tiimien oletusarvo — on rikki suurimmalle osalle ruudunlukuohjelmia käyttävistä.

5

Lopeta live-alueen ehdollinen mounttaaminen

Yleisin yksittäinen vika kaikissa neljässä kehyksessä. Mounttaa alue tyhjäksi sovelluksen käynnistyessä. Muuta tekstiä. Älä koskaan unmounttaa.


Yhteenveto: aria-live on kehysongelma naamioituneena merkintäongelmaksi

W3C ARIA -spesifikaation lukeminen jättää vaikutelman, että aria-live on merkintävalinta — polite tai assertive, roolilla status tai log tai alert, ja olet valmis. Spesifikaatio on oikein siinä mielessä, että nämä ovat ainoat kytkimet, jotka spesifikaatio tunnistaa. Spesifikaatio on myös harhaanjohtava, koska se olettaa DOM:n, joka mutoi niin kuin imperatiivinen asiakirja muutoi.

Jokainen yllä oleva kehys esittelee ajastimen koodisi ja DOM:n väliin, ja jokaisella ajastimella on reunatapauksia, joihin spesifikaatio ei vastaa — automaattinen eräajattelu, mikrotaskihuuhtelu, käännösaikainen päällekkäisyyksien poisto, signaaligrafiikit. Reunatapaukset eivät ole vikoja kehyksissä; ne ovat tarkoituksenmukaisesti suunniteltuja ominaisuuksia, jotka tapahtuvat vuorovaikuttamaan huonosti olettamusten kanssa, joita ruudunlukuohjelmat tekevät siitä, milloin DOM-mutaatiot tapahtuvat.

Korjaus on rakenteellinen, ei per-komponentti. Mounttaa globaalit live-alueet sovelluksen käynnistyessä, reititä jokainen ilmoitus pienen palvelun kautta, rajoita purskeisia lähteitä, testaa kaikilla kolmella ruudunlukuohjelmalla. Se, että sama viisivaiheinen toimintaohje toimii Reactin, Vuen, Svelten ja Solidin välillä, on vahvin todiste siitä, että valitsemallasi kehyksellä on vähemmän merkitystä kuin arkkitehtuurilla, jonka rakennat sen ympärille.

Laajemman kehittäjän Toolkitin osalta — testausmallit, käännösaikaiset tarkistukset, muu frontend-saavutettavuuskartta — katso kehittäjien laskeutumissivulta; täydellinen WCAG 2.2 onnistumiskriteerien viite indeksoi kriteerit, joita yllä olevat mallit koskevat; ilmainen WCAG 2.2 -skanneri havaitsee rakenteelliset viat, joita axe voi nähdä millä tahansa osoittamallasi URL:lla.

”Aria-live-spesifikaatio olettaa DOM:n muutoituvan tavalla kuten spesifikaatio kirjoitettiin vuonna 2008. Neljä kehystä myöhemmin mikään niistä ei muoitu niin — eikä ruudunlukuohjelma tiedä sitä.”

— Disability World engineering desk, toukokuu 2026