Koodausagenteista on tullut työkalu paitsi kehittäjille, mutta myös henkilöille, joilla ei ole koodiosaamista. Mutta miten saavutettavaa koodia kielimalleja käyttävät agentit kirjoittavat? Selvitin asiaa vertaamalla viittä eri kielimallia käyttävää koodausagenttia. Tässä blogitekstissä pääset tutustumaan havaintoihini.

Miksi agenttien saavutettavuusosaamisella on merkitystä?

Agenttinen koodaus muuttaa sovellusten kehittämistä perustavanlaatuisesti. Ensinnäkin kehittäjät hyödyntävät agentteja työssään yhä laajemmin. Stack Overflow’n Developer Survey 2025 -kyselyn mukaan yli 48 % kehittäjistä käyttää tai suunnittelee käyttävänsä agentteja työssään. Toisaalta koodaaminen onnistuu agenttien avulla helpommin myös henkilöiltä, jotka eivät ensisijaisesti ole kehittäjiä.

Agenttiselle koodaukselle tyypillistä on iteratiivisuus: ensin tehdään nopeasti toimiva versio, sitten parannellaan. Samalla koodaajan rooli muuttuu enemmän ohjaajaksi ja laadunvalvojaksi.

Esimerkiksi niin sanotussa vibe-koodauksessa käyttäjä kuvailee agentille halutun tuloksen, ja agentti luo kuvauksen perusteella toimivan sovelluksen. Käyttäjä ei välttämättä lue tai edes ymmärrä koodia, jota agentti tuottaa. Sovelluksia rakennetaan siis aiempaa enemmän idea ja toiminnallisuus edellä. Jos ensimmäinen versio näyttää ja tuntuu hyvältä, se voi päätyä tuotantoon asti.

Tässä näkökulman muutoksessa on selkeä riski saavutettavuudelle. Monet saavutettavuuspuutteet eivät näy visuaalisesti, joten ongelmat voivat jäädä helposti tunnistamatta. Siksi halusin selvittää, miten saavutettavaa koodia tekoälyagentit tuottavat silloin, kun saavutettavuutta ei erikseen mainita tehtävänannossa.

Näin vertailu tehtiin

Vertailin viittä agenttipohjaisessa koodauksessa käytettyä kielimallia yksinkertaisen koodaustehtävän avulla. Annoin kaikille agenteille saman tehtävän: niiden tuli luoda sivu, jossa käyttäjä voi hakea ja suodattaa kursseja ja tarkastella tuloksia. Sitten testasin jokaisen agentin luoman koodin saavutettavuutta.

Alusta ja mallit

Valitsin vertailualustaksi Cursorin, koska se on yleisesti käytössä oleva agentteja sisältävä koodieditori ja koska siinä voi valita agentin käyttämän kielimallin.

Mukana olivat testaushetken (joulukuu 2025) parhaimmat koodaukseen käytettävät kielimallit:

  • Googlen Gemini 3 Pro
  • Anthropicin Claude 4.5 Sonnet (thinking)
  • Anthropicin Opus 4.5 (thinking)
  • OpenAI:n GPT-5.2-Codex (high reasoning effort) (Cursorista puuttui testihetkellä uusin Codex agenttivalikoimasta, joten käytin Codex-pluginia)
  • Cursorin Composer 1

Jokainen agentti sai kolme yrityskertaa, jotta yksittäinen epäonnistuminen tai onnistuminen ei vääristä tulosta ja mallin ominaispiirteistä saa paremman kuvan.

Tehtävänanto

Pyysin agenteilta siis kurssihaku-sivua, jossa käyttäjä voi hakea ja suodattaa kursseja sekä tarkastella tuloksia.

Määrittelin promptissa seuraavat reunaehdot:

  • Rajasin tekniseksi toteutustavaksi HTML + CSS + JavaScript -kielet.
  • Kielsin UI-kirjastot ja frameworkit, jotta vertailu kohdistuu mallien tuottamaan peruskoodiin eivätkä komponenttikirjastojen valmiit ratkaisut tasoita eroja.
  • Määritin pakollisiksi ominaisuuksiksi:
    • Kurssilistan suodatus: hakusana, kategoria (monivalinta), kaupunki (pudotusvalikko), hinnan rajaus, ”vain vapaat paikat” -toggle
    • Tulosten lajittelu päivämäärän tai hinnan mukaan
    • Tulosten päivittyminen reaaliajassa sekä tulosyhteenveto
    • Aktiiviset suodattimet tageina ja suodattimien tyhjennyspainike
  • Saavutettavuutta tai mitään siihen ohjaavaa en maininnut pyynnössä lainkaan.
Tarkan promptin löydät tästä (englanniksi)
Build a small single-page web app called “Kurssihaku”. All UI text must be in Finnish.

GOAL
Users can filter a list of courses using multiple filters and see results update.

DATA
Use the dataset below directly in the app (hardcode it in app.js).

[JSON DATA]

FILTERS (AT MINIMUM)
1) Search ”Hakusana” (filter by “nimi”)
2) Category ”Kategoria” (multi-select checkboxes)
3) City ”Kaupunki” (dropdown/select)
4) Price ”Hinta” (min-max, input fields)
5) Only show courses with available seats ”Näytä vain kurssit, joissa on vapaita paikkoja” (toggle)
6) Sorting ”Lajittelu”: Date ”Päivämäärä” (newest/oldest) and Price ”Hinta” (low/high)

RESULTS
- Show “Tuloksia: X” at the top and update it whenever filters change.
- Render results as a list (cards) showing at least: nimi, kategoria, kaupunki, paivamaara, hinta, paikkoja, taso.
- If no results: show “Ei tuloksia nykyisillä suodattimilla.”
- Show active filters as removable chips/tags (removing a chip updates results).
- Add “Tyhjennä suodattimet” to reset everything.

UPDATE MODEL
- All filters must update the results immediately when they change (no separate “Apply” button).
- Search updates on every keystroke.
- Changing any checkbox/select/toggle/price input updates results right away.
- Sorting changes update results right away.

PRICE INPUT VALIDATION
- If the price inputs are invalid (e.g., min > max or not a valid number), show an error message.
- Empty min or max means “no limit”.
- Prices are non-negative numbers; 0 is valid.
- While the price inputs are invalid, do not update the results list (keep the last valid results visible).
- As soon as the inputs are corrected, update the results automatically.

SORTING BEHAVIOR
- Only one sorting option can be active at a time (choose one from: Date newest/oldest, Price low/high).

TECHNICAL CONSTRAINTS (VANILLA JS)
- Use only HTML + CSS + Vanilla JavaScript (no React, no frameworks).
- Deliver as files: index.html, styles.css, app.js.
- Must run by opening index.html in a browser (no server required).
- Dataset can live in app.js.
- No external libraries (including UI libraries).

Make the UI feel modern, clean, and pleasant to use (clear typography, good spacing, consistent alignment, and a simple card-based layout). No branding needed.

Arviointimenetelmät

Tein sivuille kevyehkön saavutettavuusauditoinnin WCAG 2.2 -kriteerejä vasten siltä osin kuin ne soveltuivat.

Testausmenetelmiin kuuluivat esimerkiksi:

  • testaus Axe DevTools-automaattityökalulla
  • kontrastien tarkistus (myös ei-tekstimuotoinen sisältö)
  • näppäimistötestaus
  • ruudunlukijatestaus: VoiceOver (macOS)
  • responsiivisuuden testaus 320 px ja suurennus 200 % saakka
  • tekstin välistyksen muutokset
  • koodin tarkastelu.

Mitä tulokset kertovat agenttien saavutettavuusosaamisesta?

Testi osoitti, että koodausagentit tuottavat usein kohtalaista perussemantiikkaa, mutta saavutettavuuden kannalta kriittiset asiat eivät synny koodiin luotettavasti ilman erillistä ohjausta. Agenttien tuottama koodi ei siis ole lähtökohtaisesti saavutettavaa, vaan vastuu jää käyttäjälle, joka ohjaa agenttia ja arvioi lopputulosta.

Yleisellä tasolla toistuvat saavutettavuuspuutteet liittyivät erityisesti seuraaviin kokonaisuuksiin:

  • lomakekenttien nimilappuihin ja ryhmittelyyn
  • kontrasteihin
  • custom-komponentteihin, jotka rikkoivat näppäimistökäyttöä
  • dynaamisiin muutoksiin, joita ei välitetty apuvälineille.

Kehityssuunta vaikuttaa silti lupaavalta, koska uudemmat mallit tuottivat vanhempia malleja valmiimman oloista kokonaiskoodia ja osoittivat enemmän ymmärrystä saavutettavuuden perusperiaatteista. Toisaalta ne myös innostuivat enemmän custom-ratkaisuista, jotka vaativat enemmän saavutettavuusosaamista.

Mielestäni testin kiinnostavin löydös oli GPT-5.2-Codexin kyky tunnistaa tilanteet, joissa apuvälinekäyttäjä tarvitsee reaaliaikaisia tilatietoja tai kontekstiin sidottuja painikkeiden nimiä. Ja tietenkin kyky toteuttaa näihin liittyvät tekniset merkinnät lähes täysin oikein. Tähän tarvitaan jo hyvää ymmärrystä ihmisen toiminnasta ja käyttötilanteista.

Auditoijan näkökulmasta tulokset olivat myös melko odotettavia. Kaikki agenttien tekemät puutteet olivat sellaisia, joita tavallisten verkkopalvelujen testauksissa tulee usein vastaan. Hieman yllättäen Codexin ratkaisut aiheuttivat suorastaan riemua agentin mainioiden oivallusten vuoksi.

Testin tulokset tarkemmin

Tässä taulukossa on koostettuna havainnot, joista kerron tarkemmin seuraavissa alaluvuissa. Taulukon luvuissa on laskettu yhteen kunkin mallin tekemät saavutettavuuspuutteet.

Taulukko tuloksista, taulukon tekstivastine löytyy kuvatekstin alta.
Koodausagenttien tekemät virheet yhteenlaskettuna. Pienempi luku tarkoittaa siis parempaa tulosta. Geminin, Composerin ja Sonnetin (*) näppäimistötestauksen tuloksia ei voi täysin verrata muihin, sillä mallit kieltäytyivät toggle-painikkeen tekemisestä.
Taulukon tekstivastine
  • Gemini 3 Pro
    • Kontrastit: 11
    • Näppäimistö: 1*
    • Ruudunlukija: 16
    • Responsiivisuus: 0
    • Muut: 0
    • Yhteensä: 28*
  • Composer 1
    • Kontrastit: 9
    • Näppäimistö: 0*
    • Ruudunlukija: 16
    • Responsiivisuus: 1
    • Muut: 0
    • Yhteensä: 26*
  • Sonnet 4.5
    • Kontrastit: 11
    • Näppäimistö: 0*
    • Ruudunlukija: 20
    • Responsiivisuus: 1
    • Muut: 0
    • Yhteensä: 32*
  • Opus 4.5
    • Kontrastit: 10
    • Näppäimistö: 6
    • Ruudunlukija: 16
    • Responsiivisuus: 2
    • Muut: 2
    • Yhteensä: 36
  • GPT-5.2-Codex
    • Kontrastit: 3
    • Näppäimistö: 3
    • Ruudunlukija: 11
    • Responsiivisuus: 0
    • Muut: 1
    • Yhteensä: 18

 

1) Lomakekentät eivät olleet saavutettavia

Testin selkein yhteinen nimittäjä eri agenttien välillä oli haasteet lomakekenttien toteutuksessa. Yksikään malli ei osannut tehdä hinnan rajaamiseen käytettäviä lomakekenttiä täysin oikein. Ongelmia oli erityisesti kenttien ryhmittelyssä sekä nimilapuissa.

Jokaisella kentällä tulee olla kentän tarkoituksen kertova nimilappu, joka on ohjelmallisesti yhdistetty syötekenttään. Toisiinsa liittyvät kentät, kuten minimi- ja maksimihinta, tulisi myös ryhmitellään ohjelmallisesti yhteen. Ilman nimilappuja ja ryhmittelyä ruudunlukijaa käyttävä henkilö ei välttämättä ymmärrä, mitä tietoa kenttään odotetaan tai mitkä kentät liittyvät toisiinsa.

Kentillä ei ollut nimilappuja

Kaikkien agenttien koodeissa hintakentiltä puuttuivat nimilaput joko

  • kokonaan
  • ohjelmallisesti (visuaalinen nimi oli, mutta label ei ollut kytketty inputiin) tai
  • visuaalisesti (label oli olemassa, mutta piilotettu).

Nimilaput oli yleensä korvattu placeholder-teksteillä, jotka katoavat heti, kun kenttään syöttää jotakin. Placeholder-teksti ei ole siksi riittävä toteutustapa saavutettavuuden näkökulmasta.

Haasteita kenttien ohjelmallisessa ryhmittelyssä

Lomakekenttien (esimerkiksi vaihtoehtojen) ohjelmallinen ryhmittely on koodausagenteille erityisen vaikeaa. Vain kaksi mallia (GPT-5.2-Codex ja Opus 4.5) osasi ylipäätään ryhmitellä kenttiä. Nämäkin onnistuivat vain 38 prosentissa niistä komponenteista, joissa ryhmittelyä olisi tarvittu (6/16). Positiivinen signaali on se, että nämä mallit olivat testin tuoreimpia.

Hintakenttien ryhmittely osoittautui todelliseksi haasteeksi. Ainoastaan Opus osasi yhdessä yrityksessään yhdistää minimi- ja maksimihinnan kenttäryhmäksi fieldset/legend-rakenteella.

2) Toistuvia puutteita kontrasteissa

Tekstin ja taustan sekä käyttöliittymäelementtien kontrastien tulee täyttää WCAG-vaatimukset kaikissa tiloissa. Riittämätön kontrasti vaikeuttaa lukemista ja hahmottamista erityisesti heikkonäköisille käyttäjille ja kirkkaissa valaistusolosuhteissa.

Kontrastipuutteet olivat testissä käytännössä enemmän sääntö kuin poikkeus. Puutteita löytyi kaikista käyttöliittymistä, yleensä monesta eri komponentista. Tyypillisiä kompastuskiviä olivat kurssien tiedoista kertovat pienet tekstit sekä valittuja suodattimia esittävät chip-elementit. Myös hintakenttien virheistä ilmoittavat tekstit olivat systemaattisesti pielessä, lukuun ottamatta Codexin käyttöliittymiä.

Lisäksi kaikki agentit toistivat saavutettavuusauditoinneissakin usein vastaan tulevan kontrastipuutteen: jokainen pois päältä oleva toggle-painike oli liian haalea taustaansa verrattuna.

Selkeästi vähiten kontrastipuutteita oli GPT-5.2-Codexilla, jonka ainoa virhe olikin edellä mainittu toggle-painike.

3) Näppäimistökäyttö toimi natiivielementeissä, custom-ratkaisut aiheuttivat haasteita

Kaikkien interaktiivisten elementtien tulee olla käytettävissä näppäimistöllä ja niissä tulee olla selkeästi näkyvä kohdistus. Näppäimistösaavutettavuus oli melko hyvällä tasolla silloin, kun agentit käyttivät natiiveja HTML-elementtejä. Ongelmia kuitenkin esiintyi aina, kun mukana oli custom toggle-, radio- tai checkbox -ratkaisuja. Näissä tyypillinen toteutustapa on piilottaa alkuperäinen HTML-elementti visuaalisesti ja rakentaa sen päälle tyylitelty versio. Toimivuus näppäimistöllä täytyy silloin luoda erikseen.

Näppäimistötestauksesta löytyi seuraavia havaintoja:

  • Opus 4.5 toteutti eniten omia valintapainikkeita, eikä missään niistä ollut näkyvää näppäimistökohdistusta.
  • Yhdenkään agentin toggle-painike ei saanut näkyvää kohdistusta. Geminin toggle-painiketta ei voinut käyttää ollenkaan näppäimistöllä.
  • Täysin puhtaat paperit näppäimistötestauksesta saivat kaksi mallia: Claude Sonnet 4.5 ja Cursorin Composer 1. Ne eivät kuitenkaan suostuneet toteuttamaan yhtäkään toggle-painiketta vaan käyttivät sen sijaan valintaruutua, joten tulosta ei välttämättä voi verrata muihin.

4) Käyttäjälle ei annettu tietoa dynaamisten hakutulosten päivittymisestä

Hakutulossivulla suodattaminen ja hakeminen on dynaamista, eli tuloslista päivittyy ilman sivun erillistä päivittämistä. Saavutettavuuden kannalta on oleellista, että apuvälinekäyttäjälle kerrotaan, kun sivun sisältö muuttuu.

Tässä testissä vain yksi malli (GPT-5.2-Codex) yritti välittää päivittymistä ruudunlukijalle. Toteutus oli kuitenkin hieman kömpelö: aria-live=”polite” oli asetettu koko hakutulosalueelle, joten ruudunlukija luki kaikki alueen sisällä tapahtuvat muutokset. Esimerkiksi kun uusia kurssikortteja ilmestyi tuloksiin, ruudunlukija luki kaikkien kurssien kaikki tiedot ääneen.

Lisäksi määrittely oli virheellinen, sillä ruudunlukija kertoi ainoastaan muuttuvan sisällön. Jos siis osa sisällöstä pysyi samana (kuten esimerkiksi ”Näytetään 5 kurssia”-tekstissä ainoastaan numero vaihtuu), käyttäjälle välittyi vain muuttuva numero ilman kontekstia.

Käyttäjän kannalta parempi malli olisi rajata live-alue vain pieneen statusviestiin (esim. ”Näytetään 5 kurssia”). Codexin olisi tullut lisätä aria-live=”polite”-määrityksen lisäksi aria-atomic=”true”, jotta käyttäjälle luettaisiin koko statusviesti eikä pelkästään muutos. Vielä helpompi ratkaisu olisi käyttää viestialueessa roolia role=”status”.

Tästä huolimatta pidän Codexin yritystä lupaavana signaalina: tämä vertailun tuorein agentti tajusi, että dynaaminen muutos pitää kertoa ohjelmallisesti.

5) Suodattimen poistopainikkeen tarkoitus ei aina välittynyt

Suurin osa malleista nimesi chipin (valitun suodattimen) poistopainikkeen niin, että ruudunlukija luki painikkeen kohdalla ”x” tai ”kertomerkki” ilman toiminnon nimeä tai kontekstia (mitä poistetaan?). Painikkeen saavutettavasta nimestä tulisi aina selvitä, mitä sen aktivoinnista tapahtuu.

Codexin versioissa nähtiin parempaa otetta: poistopainikkeeseen liitettiin myös suodattimen nimi (”Poista suodatin: Ilmainen”), jolloin käyttö muuttuu välittömästi selkeämmäksi. Näin hyviä toteutuksia tulee harvoin auditoinneissakaan vastaan, joten Codex osasi todella yllättää positiivisesti.

6) Virheviestien toteutuksissa oli ongelmia

Hinnan rajaamisen kentissä käyttäjälle näytettiin virheviesti, jos minimihinta oli suurempi kuin maksimihinta. Tällaiset virheilmoitukset tulee merkitä niin, että ne välittyvät apuvälineille ilman että käyttäjän täytyy erikseen siirtyä viestin kohdalle. Ainoastaan Codex osasi merkitä virheviesteihin role=”alert”, jolloin ilmoitus luetaan ruudunlukijalla välittömästi.

Lomakekenttiin liittyvät virheviestit tulee myös yhdistää kenttiin ohjelmallisesti, jotta viesti luetaan ruudunlukijalla aina kun käyttäjä siirtyy virheelliseen kenttään. Yhdistäminen tapahtuu tyypillisesti aria-describedby-attribuutin avulla. Yksikään malleista ei tehnyt tätä.

7) Otsikkojen käytössä oli pieniä puutteita

Otsikkona toimivat ja otsikon näköiset tekstit oli yleensä tunnollisesti merkitty, ja otsikkotasoja oli käytetty johdonmukaisesti. Testeissä toistui kuitenkin yksi virhe: hakutulosalueelta puuttui usein oma otsikko, joten hakutulokset jäsentyivät otsikkohierarkiassa suodatinalueen (”Suodattimet”) alle. Ruudunlukijalla ratkaisu on epälooginen, koska hakutulokset eivät ole suodattimien alakohta, vaan erillinen osionsa.

Osa malleista oli kuitenkin lisännyt hakutuloksille yläotsikoksi ”Kurssit” tai merkinnyt hakutulosten määrän (”Tuloksia: 5”) otsikoksi. Molemmat ovat hyviä tapoja ja jäsentävät sivua.

Mitä voimme oppia testistä?

Tärkein käytännön oppi on, että ensimmäinen agentin tuottama versio ei ole valmis sellaisenaan. Oleellista on puutteiden tunnistaminen ja korjaaminen seuraavilla pyynnöillä. Todennäköisesti vertailun tulokset olisivat olleet paljon valoisammat, jos testiin olisi sisältynyt yksi korjauskehote.

Kun koodausagentit ja systeemit niiden ympärillä kehittyvät, tulee saavutettavasta koodistakin helpompaa toteuttaa. Jo nyt agenttikoodauksessa voidaan hyödyntää työkaluja, joilla agentin saavutettavuusosaamista voi kehittää tai koodia testata automaattisesti. Toistaiseksi testityökalut eivät kuitenkaan pysty tunnistamaan monia vuorovaikutuksessa syntyviä saavutettavuuspuutteita.

Puutteiden havaitsemiseen tarvitaan siis saavutettavuusosaamista, sillä tässäkin testissä ainoastaan tekstikontrastien puutteita löytyi automaattityökalulla. Kaikki muut löydökset vaativat näppäimistötestausta, ruudunlukijatestausta tai koodin ja käyttöliittymän tarkastelua.

Jos käytät koodausagentteja omissa projekteissasi, muista:

  1. Testaa aina näppäimistöllä ja ruudunlukijalla, koska automaattityökalut löytävät vain osan ongelmista.
  2. Tarkista erityisesti lomakekenttien labelit ja ryhmittely.
  3. Validoi kontrastit myös manuaalisesti (esim. Colour Contrast Analyser on hyvä työkalu tähän).