<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ceci Olivera</title>
    <description>The latest articles on DEV Community by Ceci Olivera (@cec1_c0d).</description>
    <link>https://dev.to/cec1_c0d</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F264034%2Fea3894bf-4fb4-4d95-95ed-ee46d88a183d.jpg</url>
      <title>DEV Community: Ceci Olivera</title>
      <link>https://dev.to/cec1_c0d</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/cec1_c0d"/>
    <language>en</language>
    <item>
      <title>An MCP for the most common failure of the Web</title>
      <dc:creator>Ceci Olivera</dc:creator>
      <pubDate>Mon, 29 Jun 2026 06:00:00 +0000</pubDate>
      <link>https://dev.to/cec1_c0d/an-mcp-for-the-most-common-failure-of-the-web-php</link>
      <guid>https://dev.to/cec1_c0d/an-mcp-for-the-most-common-failure-of-the-web-php</guid>
      <description>&lt;p&gt;There's a deterministic algorithm at the center of this story and a probabilistic model at the edge of it, and most of what I learned lives in the space between them.&lt;/p&gt;

&lt;p&gt;Here's the short version: a designer friend of mine, Marta Herrera Hollingsworth, built, years ago, an algorithm that generates monochromatic color palettes whose contrast relationships are guaranteed mathematically. I turned that algorithm into a library, and then wrapped the library in an MCP (Model Context Protocol) server so that any AI agent building a UI could call it and get colors that can't fail WCAG 2.2 contrast. This is the story of that library, why it exists, the surprisingly stubborn problem of getting models to actually use its output as written, and where it all landed: a better place than where it started, though not the place I expected at the outset.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why color, of all things
&lt;/h3&gt;

&lt;p&gt;WebAIM scans the homepages of the top million most-visited sites in the world every year, and for seven years straight, low-contrast text has been the single most common accessibility failure they can detect. The February 2026 report found it on 83.9% of homepages, up from 79.1% the year before, averaging 34 separate instances per page. This isn't an edge case. It's the default condition of the web.&lt;/p&gt;

&lt;p&gt;And the web is increasingly being written by agents. Microsoft's A11y LLM Eval harness (built by Michael Fairchild and collaborators) measures how accessible model-generated code actually is, and the baseline is bleak: with no accessibility guidance, eight frontier models passed the harness's automated checks only about 12% of the time, with contrast errors featuring heavily. The hopeful part of that same report is that guidance works: a short "please be accessible" instruction lifted pass rates by roughly 24 points, and a structured skill that made the model review its own output did even better, resolving on average 86% of the issues automated tools can measure — with color contrast still the single most persistent problem, even then.&lt;/p&gt;

&lt;p&gt;So: a perfect place to put a tool.&lt;/p&gt;

&lt;h3&gt;
  
  
  What MCP is, and why I used it here
&lt;/h3&gt;

&lt;p&gt;Before getting into the library, it's worth explaining the scaffolding, because the choice of protocol matters.&lt;/p&gt;

&lt;p&gt;MCP (Model Context Protocol) exposes three distinct primitives a model can draw on during generation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tools&lt;/strong&gt;: functions that execute logic (calculations, validations) and return structured data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resources&lt;/strong&gt;: data the model reads as a persistent, URI-addressable source of truth, rather than the one-off result of a single call.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prompts&lt;/strong&gt;: reusable instruction flows that the server itself exposes, instead of leaving every integration to reinvent them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A REST API with function calling also gives you tools — that part isn't new. What it doesn't give you, without extra convention, is a standard way to expose a URI-readable source of truth, or a forced sequence of steps that any MCP client understands the same way. That's why I chose MCP: because &lt;code&gt;palette://{hex}/{theme}&lt;/code&gt; is a resource the agent can return to at any point during generation, and because I thought a prompt could be the flow that forced the right order of steps. I was wrong about that second part, and I'll come back to it later.&lt;/p&gt;

&lt;h3&gt;
  
  
  What the library actually does
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/ceciCoding/accessible-color-palette" rel="noopener noreferrer"&gt;The library&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Give it one seed color and a theme — whether it'll sit on white or black — and it produces a small monochromatic scale, six shades across a 100–900 range, where every contrast relationship has been computed. But the colors are the easy part. The valuable part is what the library ships alongside them: a compatibility matrix. For each shade, it tells you exactly which other shades (plus pure white and black) are safe to use as text on top of it at WCAG 2.2 AA, and which pairings hold only for large text.&lt;/p&gt;

&lt;p&gt;Over MCP it offers several ways in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;generate_palette&lt;/code&gt;: returns the palette and its full compatibility matrix as structured data.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;validate_pairings&lt;/code&gt;: takes a list of intended foreground/background pairs and grades each one against that matrix before any CSS gets written, answering with a blunt &lt;code&gt;proceed: true&lt;/code&gt; or &lt;code&gt;proceed: false&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;generate_css_tokens&lt;/code&gt;: returns a ready-to-paste &lt;code&gt;:root {}&lt;/code&gt; block. It has an entry condition I'll get to later, because it changes something important about what follows.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;check_contrast&lt;/code&gt;: a free-form contrast check between any two hex values, independent of a generated palette. The base scale is monochromatic by design — one hue, varying only in lightness — and that's exactly what makes it mathematically predictable. But almost no real interface lives on that alone: an error state, a sale badge, a brand accent all need a color the monochromatic scale doesn't have. &lt;code&gt;check_contrast&lt;/code&gt; exists so that decision also runs through arithmetic instead of the model's eye. I'll come back to a problem this doesn't fully solve.&lt;/li&gt;
&lt;li&gt;A resource, &lt;code&gt;palette://{hex}/{theme}&lt;/code&gt;, that lets an agent pull the whole matrix by URI before making a single color decision.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is exactly the kind of problem you want to hand off to a tool instead of a model, and the reason is precise: contrast is arithmetic. Working out the luminance ratio between two hex values is a calculation, and calculations are the kind of work you should keep out of a system that predicts text. The whole idea is to take that math out of the model's probabilistic "head" and put it into a tool that returns a deterministic answer.&lt;/p&gt;

&lt;p&gt;That was the bet. Expose the library over MCP, and contrast failures should become close to impossible.&lt;/p&gt;

&lt;h3&gt;
  
  
  The diagnosis: why the model ignores the rules
&lt;/h3&gt;

&lt;p&gt;They didn't become impossible — though the model never ignored the palette. It called the tool, received the output with every color and every rule intact, and did something more persistent: it rewrote the CSS from scratch, in its own conventions, its own variable names, its own structure. And, crucially, without the compatibility rules — the entire point of the tool — which got lost somewhere between reading the output and writing the page.&lt;/p&gt;

&lt;p&gt;This wasn't constant or catastrophic. The failures were a minority, but persistent, and they always had the same shape: the safe-pairing information, the whole reason the tool exists, evaporated on the trip from the tool's output to the final stylesheet.&lt;/p&gt;

&lt;p&gt;Why does this happen? It isn't an instruction-following failure. It's something simpler and harder to fight: a model trained on an enormous amount of CSS has a very strong sense of what well-written CSS looks like, and when it receives a piece of CSS-shaped data, it doesn't copy it — it regenerates it in that learned shape. The accessibility annotations were never part of that learned shape and were just comments above the &lt;code&gt;:root {}&lt;/code&gt; block, so they don't survive the regeneration.&lt;br&gt;
Generally, the System Prompt carries more hierarchical weight because it defines the model's "character" and global operating rules. However, even a very strong prompt can lose out to exposure bias (the examples the model itself has just generated) or to the training bias mentioned earlier.&lt;/p&gt;

&lt;p&gt;The pattern is recognizable: the model reliably keeps the parts of the output it can't reconstruct from memory (a specific hex value) and rewrites the parts it can — the structure, the variable names, the comment formatting. There's no disobedience in that. It's the path of least resistance for a system that generates text token by token, and the path of least resistance is always the CSS it's already seen millions of times. No amount of "IMPORTANT: do not rewrite this" reliably beats that, because the instruction is just text, competing against the model's entire sense of the medium.&lt;/p&gt;

&lt;p&gt;This reframes the problem: if the instruction loses almost every time against the training prior, the only lever left isn't asking the model to behave differently. It's changing the format of what it receives, so the correct option is cheaper to copy than to rewrite.&lt;/p&gt;
&lt;h3&gt;
  
  
  Three ways to investigate this without being an AI researcher
&lt;/h3&gt;

&lt;p&gt;One part of the process I don't want to hide: I'm not an AI researcher, and I don't understand a transformer's internal mechanics at the level of the research papers. But that doesn't stop me from reasoning well about how one behaves, and I did it by drawing on three different sources, each with a blind spot the other two didn't share.&lt;/p&gt;

&lt;p&gt;The first was a NotebookLM loaded with a dozen papers on LLMs, used as an oracle to ask plain-language questions: why does a model ignore explicit instructions but copy certain formats, what makes a piece of text easier to regenerate than to copy. From there came the hypothesis I eventually tested: that the difference between a block comment and an inline one isn't cosmetic — it changes how hard it is for the model to separate the data from the structure around it. &lt;br&gt;
That same back-and-forth with the oracle also produced something more structural: a priority hierarchy that explains why compliance with logical rules can consistently lose against the statistical biases of training. That mental model is what let me turn my failed attempts into the architecture the project rests on today:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Level&lt;/th&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Weight / Nature&lt;/th&gt;
&lt;th&gt;Model Interaction&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Training Prior&lt;/td&gt;
&lt;td&gt;Highest. The statistical "gravitational pull."&lt;/td&gt;
&lt;td&gt;It wins by default. Models prefer to regenerate familiar shapes learned from billions of tokens rather than copying bespoke rules that feel "unnatural" to their training.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;System Prompt&lt;/td&gt;
&lt;td&gt;High (Structural). Defines persona and operational boundaries.&lt;/td&gt;
&lt;td&gt;It sets the stage, but it is text-based. It can be overridden by the Prior or forgotten in long context windows ("Lost in the Middle").&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Tools / MCP&lt;/td&gt;
&lt;td&gt;Medium (Logical). External functions for deterministic logic.&lt;/td&gt;
&lt;td&gt;The model treats tool output as "more text" to improvise on. The oracle's own framing for beating the Prior here is Read-Plan-Generate — forcing the model to plan and validate before it generates. It works, but only when something actually forces the sequence; left as a suggestion, the model skips straight to generating.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Skills&lt;/td&gt;
&lt;td&gt;Variable. Learned capabilities (e.g., coding, math).&lt;/td&gt;
&lt;td&gt;Are they system prompts? Usually not. Skills are capabilities baked into the weights through SFT/RLHF. You invoke a skill via a prompt, but the skill itself is part of the model's internal repertoire.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The second was the implementing model, in actual working sessions: it proposed concrete formats, and by what it preserved versus what it stripped out when generating code, it revealed which of those formats actually held up under regeneration.&lt;/p&gt;

&lt;p&gt;The third was the most direct: asking the models themselves, after they'd generated something, why they'd done what they did. I don't take that source at face value. A model explaining its own behavior is, at best, a plausible reconstruction, not real introspection — but more than once those answers pointed, in their own words, at exactly the spot where the tool was failing. One of them ended up proposing two reasonable architectural fixes. None of the three sources alone would have gotten me here.&lt;/p&gt;
&lt;h3&gt;
  
  
  First solution: inline comments
&lt;/h3&gt;

&lt;p&gt;Once the diagnosis was clear, the first fix was a matter of format, not instruction.&lt;/p&gt;

&lt;p&gt;A block comment at the top of the file is, to the model, separable documentation: it gets removed at no cost, because dropping a whole block doesn't break anything around it. An inline comment, pinned to the end of a declaration, is different: removing it means editing that specific line, one at a time, instead of discarding an entire block in one move. That extra friction is enough that, most of the time, the model doesn't bother.&lt;/p&gt;

&lt;p&gt;The winning format was this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nd"&gt;:root&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="py"&gt;--color-100&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#faf2f5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* ✅ text→900·800·700  ⚠️ lg→600 */&lt;/span&gt;
    &lt;span class="py"&gt;--color-300&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#e9bfcc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* ✅ text→900·800  ⚠️ lg→700 */&lt;/span&gt;
    &lt;span class="py"&gt;--color-600&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#c86f90&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* ✅ text→900  ⚠️ lg→100·800·white */&lt;/span&gt;
    &lt;span class="py"&gt;--color-700&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#bb4268&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* ✅ text→white·100  ⚠️ lg→300·900 */&lt;/span&gt;
    &lt;span class="py"&gt;--color-800&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#67273f&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* ✅ text→white·100·300  ⚠️ lg→600 */&lt;/span&gt;
    &lt;span class="py"&gt;--color-900&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#3b1521&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* ✅ text→white·100·300·600  ⚠️ lg→700 */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each variable carries, soldered on as an inline comment, the exact list of backgrounds it's safe on. The model copies the whole block, &lt;code&gt;:root {}&lt;/code&gt; included, because separating the rule from the variable costs more than copying both together.&lt;/p&gt;

&lt;p&gt;Putting the comment inline at the end of the variable creates a syntactic bond (contextual binding). To the model, the CSS variable token and the accessibility rule token sit so close together in attention space that pulling them apart would mean swimming against its own training. The comment gets processed as part of the declaration's logical unit.&lt;/p&gt;

&lt;p&gt;Getting here took several failed attempts, and each one taught something. An all-caps warning inside a comment ("COPY AS-IS") changed nothing — a comment that orders you to copy it is, to the model, still just a comment from a tool that doesn't really have enforcement capabilities. A plain-text table placed above the CSS got read and ignored just the same, because the model wrote its own CSS regardless. The format that worked wasn't the most human-readable one — it was the one that most closely resembled what the model already wanted to write.&lt;/p&gt;

&lt;h3&gt;
  
  
  Second attempt: the forced planning flow
&lt;/h3&gt;

&lt;p&gt;Inline comments solve what survives once the model has the CSS in hand. They don't solve whether the model arrived at that CSS having checked the pairs before writing anything. For that I built &lt;code&gt;plan-palette-usage&lt;/code&gt;, an MCP prompt that structures the whole sequence into four steps: read the matrix, plan each pair in plain text before writing any CSS, validate that list with &lt;code&gt;validate_pairings&lt;/code&gt;, and only generate tokens if the whole validation came back clean.&lt;/p&gt;

&lt;p&gt;The idea was to turn a judgment problem, which models handle inconsistently, into a step-execution problem, which they handle better. It worked, when invoked. The problem, which took me a while to see clearly, was in that condition.&lt;/p&gt;

&lt;h3&gt;
  
  
  The nuance that changed everything: prompts need a human
&lt;/h3&gt;

&lt;p&gt;I assumed the model could trigger &lt;code&gt;plan-palette-usage&lt;/code&gt; on its own, the same way it triggers any other tool when it judges that doing so helps. That's false, and not because of some particular client's limitation — it's true by specification.&lt;/p&gt;

&lt;p&gt;Of MCP's three primitives, &lt;strong&gt;tools&lt;/strong&gt; are model-controlled: the model decides when to call them. &lt;strong&gt;Prompts&lt;/strong&gt; are user-controlled: they only fire if a person invokes them explicitly. What changes between clients is the shape of that invocation, not who triggers it. In Claude Code, "explicitly" means literally: you need to type the exact command, &lt;code&gt;/mcp:accessible-palette:plan-palette-usage&lt;/code&gt;, autocomplete included; asking the model in natural language to "use prompt X" isn't enough. In Cline, it is enough — if I ask it in my own message to run &lt;code&gt;plan-palette-usage&lt;/code&gt;, it invokes the prompt without me typing the command. But the invocation still starts from a person requesting it by name or intent, not from the model deciding on its own that it was worth using. The barrier that matters for this story isn't the command's exact format: it's that in no client does the model trigger it by itself, unprompted.&lt;/p&gt;

&lt;p&gt;In practice, this means the four-step flow almost never runs. Nobody is going to teach a non-technical person the exact command or the exact phrase needed to trigger a guided prompt when they just ask for a landing page with something like "make me a page about X using the palette mcp." And this library's accessibility guarantee couldn't depend on that.&lt;/p&gt;

&lt;h3&gt;
  
  
  Third solution: the gate on the server
&lt;/h3&gt;

&lt;p&gt;This is what actually pushed me to change layers: if the model can't invoke the prompt on its own, then the whole guarantee rested on a mechanism that, in real-world use, almost never fires. I needed something that didn't depend on someone typing a command.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;validate_pairings&lt;/code&gt; always computed contrast deterministically — that was never the weak point. The weak point was that &lt;code&gt;generate_css_tokens&lt;/code&gt;, the tool that actually delivers the CSS, knew nothing about what had happened before it. You could skip validation entirely and ask for the tokens anyway, and the server would hand them over without asking. &lt;code&gt;proceed: false&lt;/code&gt; was a good-faith convention between the prompt and the model; the server had no memory of whether that convention had been honored.&lt;/p&gt;

&lt;p&gt;The fix, in code, was small: the server now keeps a record of which hex-and-theme combinations have already passed &lt;code&gt;validate_pairings&lt;/code&gt; successfully, and &lt;code&gt;generate_css_tokens&lt;/code&gt; checks that record before generating anything. If the combination hasn't been validated, the tool throws an error and refuses to respond — the same way it fails on an invalid hex, not as a warning the model can choose to read or ignore.&lt;/p&gt;

&lt;p&gt;Technically, that record is an in-memory &lt;code&gt;Set&lt;/code&gt;, instantiated once when the Node process starts. The transport is &lt;code&gt;StdioServerTransport&lt;/code&gt;, so every client that launches the server spins up a fresh process, and "session" means nothing more sophisticated than that process's lifetime. It doesn't persist to disk, has no TTL. It's deliberately the simplest unit that could work, and — crucially — it doesn't depend on anyone writing anything by hand: it lives inside a tool the model was already going to call anyway.&lt;/p&gt;

&lt;p&gt;What this changes is where the guarantee lives. Before, it depended on a person knowing how to invoke a prompt, and on the model honoring a text-based convention. Now there's no decision to make: either validation already passed, in this same process, for this hex and this theme, or the tool produces no output.&lt;/p&gt;

&lt;p&gt;I'm still being honest about the real limit: this doesn't stop an agent from writing CSS by hand, bypassing the tool entirely, with whatever colors it likes. No MCP server can prevent that; it's outside its reach. What the gate guarantees is narrower, and at the same time sturdier: if the agent uses this tool to get the tokens, there's no way to get them without having validated first.&lt;/p&gt;

&lt;h3&gt;
  
  
  How it all landed
&lt;/h3&gt;

&lt;p&gt;What holds the system up today is, in essence, a single piece: the server-side gate, backed by the inline comments so that what's been validated doesn't get lost on the way out. The prompt still exists, still invokable by anyone who knows the command, but I'm no longer sure what real advantage it has over the gate. In theory it forces a re-read of the matrix and explicit reasoning before validating; in practice, I have no evidence that changes the final outcome in any way the gate doesn't already achieve on its own. I'm documenting it for what it is: an optional add-on, not the mechanism anything important depends on.&lt;/p&gt;

&lt;p&gt;I want to be concrete about what "works" means here, rather than settling for "I've used it and it's gone well." I generated 45 demo pages across several different agents and models, running on Open Code, Claude Code, GitHub Copilot, and Cline, using only minimal natural-language prompts like "build a landing page about X using the palette mcp," never invoking the guided prompt, and combining this with the accessibility skill from &lt;code&gt;github/awesome-copilot&lt;/code&gt; (the same one its author, Michael Fairchild, tuned against his own evaluation harness before publishing it). I ran &lt;a href="https://github.com/dequelabs/axe-core" rel="noopener noreferrer"&gt;axe-core&lt;/a&gt; against all 45: &lt;strong&gt;35 had zero violations of any kind&lt;/strong&gt;. Of the 10 that did, 7 were specifically color-contrast violations — the exact problem this library exists to solve, still slipping through, but in a small fraction of the total. I'd like to point out that this sample is extremely small. I encourage you to try it yourself, however you consider. Screenshots and full details are in the repository, under &lt;a href="https://github.com/ceciCoding/accessible-monochrome-palette/tree/main/demo" rel="noopener noreferrer"&gt;/demo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy4prh94w2rhhq5q32wzr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy4prh94w2rhhq5q32wzr.png" alt="One of the demo samples. A landing page for a summer camp using green and yellow shades" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It doesn't always come out perfect: every so often a failure still slips through. Not in the tokens the server delivers anymore — those are mathematically closed — but in the final component CSS, where the model can still ignore the manifest if it decides to write by hand outside the tool, or in accent colors, where I don't yet have the same kind of guarantee. Language model behavior is probabilistic, and no patch — not even one that lives on the server — is airtight against a formatting prior operating outside the protocol.&lt;/p&gt;

&lt;p&gt;I'm not going to put my own benchmark number on this beyond what I've already shared: accessibility is context-dependent, and your model, your prompts, your task mix all move the result. So here's a direct invitation: &lt;strong&gt;install the server, try it against your own workflows&lt;/strong&gt;, run your own axe-core or Lighthouse against what it generates, and tell me what you get. If you find a case where the model jumps the rails, or a format that works better than mine, I want to know — that's the only way to keep refining this.&lt;/p&gt;

&lt;p&gt;What I do stand behind, without reservation, is the direction. A handful of occasional misses, now confined to the part of the process the protocol can't reach, lives in a completely different world from "low-contrast text on 84% of pages" or a 12% unguided pass rate. You're not installing a hard constraint on the whole process; you're installing a hard constraint on the arithmetic part, and leaning hard, with text and with structure, on the part that's still probabilistic.&lt;/p&gt;

&lt;h3&gt;
  
  
  What comes next
&lt;/h3&gt;

&lt;p&gt;Two things remain open, and I'd rather name them with the same honesty as the rest of this piece than give the impression there's nothing left to fix here.&lt;/p&gt;

&lt;p&gt;The first is mine: finding an equivalent, for accent colors, of what inline comments achieve for the base palette — so that an accent validated with &lt;code&gt;check_contrast&lt;/code&gt; ends up somehow soldered to the CSS, instead of losing that validation on the first edit.&lt;/p&gt;

&lt;p&gt;The second is Marta's: she's working on adapting the original algorithm to APCA (Accessible Perceptual Contrast Algorithm), the perceptual contrast method being defined for WCAG 3.0.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;The palette algorithm was created by &lt;a href="https://www.linkedin.com/in/hollyheh/" rel="noopener noreferrer"&gt;Marta Herrera Hollingsworth&lt;/a&gt;; the library and the MCP server are mine. This piece is a practical companion to my earlier essay, "As We May Code: Why Software Is a Human Problem Dressed in Logic."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Motivation data: &lt;a href="https://webaim.org/projects/million" rel="noopener noreferrer"&gt;WebAIM Million 2026&lt;/a&gt; and the &lt;a href="https://microsoft.github.io/a11y-llm-eval-report" rel="noopener noreferrer"&gt;A11y LLM Eval&lt;/a&gt; report by Michael Fairchild.&lt;/em&gt; &lt;em&gt;I'm part of the &lt;a href="https://accessibility.github.com/accessibility-advisory-panel.html" rel="noopener noreferrer"&gt;GitHub Accessibility Advisory Panel&lt;/a&gt;; that's where I came across Michael Fairchild's work, which motivated this project.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>a11y</category>
      <category>ai</category>
      <category>webdev</category>
      <category>mcp</category>
    </item>
    <item>
      <title>Un MCP para el fallo más común de la web</title>
      <dc:creator>Ceci Olivera</dc:creator>
      <pubDate>Mon, 29 Jun 2026 06:00:00 +0000</pubDate>
      <link>https://dev.to/cec1_c0d/un-mcp-para-el-fallo-mas-comun-de-la-web-5f5</link>
      <guid>https://dev.to/cec1_c0d/un-mcp-para-el-fallo-mas-comun-de-la-web-5f5</guid>
      <description>&lt;p&gt;Hay un algoritmo determinista en el centro de esta historia y un modelo probabilístico en el borde, y la mayor parte de lo que aprendí vive en el espacio que hay entre ellos.&lt;/p&gt;

&lt;p&gt;Versión corta: una amiga diseñadora, Marta Herrera Hollingsworth, construyó, hace años, un algoritmo que genera paletas monocromáticas cuyas relaciones de contraste están garantizadas matemáticamente. Convertí ese algoritmo en una librería, y luego envolví la librería en un servidor MCP (Model Context Protocol) para que cualquier agente de IA que construya una interfaz pueda llamarlo y obtener colores que no pueden fallar el contraste WCAG 2.2. Esta es la historia de esa librería, de por qué existe, del problema sorprendentemente obstinado de lograr que los modelos usen su output tal como está escrito, y de dónde terminó todo: en un lugar mejor que donde empezó, aunque no el lugar que esperaba al principio.&lt;/p&gt;

&lt;h3&gt;
  
  
  Por qué el color, de entre todas las cosas
&lt;/h3&gt;

&lt;p&gt;WebAIM escanea la home del millón de páginas más visitadas del mundo cada año, y durante siete años consecutivos, el texto de bajo contraste ha sido el fallo de accesibilidad más común que pueden detectar. El informe de febrero de 2026 lo encontró en el 83,9% de las páginas de inicio, frente al 79,1% del año anterior, con una media de 34 instancias separadas por página. Esto no es un caso límite. Es el estado base de la web.&lt;/p&gt;

&lt;p&gt;Y la web está siendo escrita cada vez más por agentes. El A11y LLM Eval harness de Microsoft (construido por Michael Fairchild y colaboradores) mide lo accesible que es realmente el código generado por modelos, y la línea de base es sombría: sin orientación sobre accesibilidad, ocho modelos de frontera superaron las comprobaciones automatizadas del harness solo aproximadamente el 12% de las veces, con errores de contraste muy presentes. La parte esperanzadora de ese mismo informe es que la orientación funciona: una breve instrucción de "debe ser accesible" elevó las tasas de aprobación en unos 24 puntos, y una skill estructurada que hacía que el modelo revisara su propia salida funcionó aún mejor, resolviendo de media el 86% de los problemas que las herramientas automáticas pueden medir, siendo el contraste de color el problema que más persiste incluso así.&lt;/p&gt;

&lt;p&gt;Este es un lugar perfecto para poner una herramienta.&lt;/p&gt;

&lt;h3&gt;
  
  
  Qué es MCP y por qué lo usé aquí
&lt;/h3&gt;

&lt;p&gt;Antes de entrar en la librería, vale la pena explicar el andamiaje, porque la elección del protocolo importa.&lt;/p&gt;

&lt;p&gt;MCP (Model Context Protocol) expone tres primitivas distintas que un modelo puede consumir durante su generación:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Tools&lt;/strong&gt;: funciones que ejecutan lógica (cálculos, validaciones) y devuelven datos estructurados.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Resources&lt;/strong&gt;: datos que el modelo lee como una fuente de verdad persistente, identificable por URI, no como el resultado puntual de una llamada.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Prompts&lt;/strong&gt;: flujos de instrucción reutilizables que el propio servidor expone, en lugar de dejar que cada integración los reinvente.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Una API REST con function calling también te da tools, eso no es nuevo. Lo que no te da, sin convención adicional, es una forma estándar de exponer una fuente de verdad legible por URI o un flujo de pasos forzado que cualquier cliente MCP entienda igual. Elegí MCP por eso: porque&amp;nbsp;&lt;code&gt;palette://{hex}/{theme}&lt;/code&gt;&amp;nbsp;es un recurso al que el agente puede volver en cualquier momento de la generación, y porque pensé que un prompt podía ser el flujo que forzara el orden correcto de pasos. Sobre esa segunda idea me equivoqué, y vuelvo a ello más adelante.&lt;/p&gt;

&lt;h3&gt;
  
  
  Qué hace la librería
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/ceciCoding/accessible-color-palette" rel="noopener noreferrer"&gt;La librería&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Dale un color base y un tema (si se va a asentar sobre blanco o negro), y produce una pequeña escala monocromática, seis tonos en un rango 100–900, donde cada relación de contraste ha sido calculada. Pero los colores son la parte fácil. La parte valiosa es lo que la librería entrega junto a ellos: una matriz de compatibilidad. Para cada tono, te dice exactamente qué otros tonos (además de blanco y negro puros) son seguros para usar como texto sobre él en WCAG 2.2 AA, y qué emparejamientos solo son válidos para texto grande.&lt;/p&gt;

&lt;p&gt;A través de MCP ofrece varias vías de entrada:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;generate_palette&lt;/code&gt;: devuelve la paleta y su matriz de compatibilidad completa como datos estructurados.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;validate_pairings&lt;/code&gt;: toma una lista de pares (primer plano/fondo) y califica cada uno contra esa matriz antes de que se escriba CSS, respondiendo con un contundente&amp;nbsp;&lt;code&gt;proceed: true&lt;/code&gt;&amp;nbsp;o&amp;nbsp;&lt;code&gt;proceed: false&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;generate_css_tokens&lt;/code&gt;: devuelve un bloque&amp;nbsp;&lt;code&gt;:root {}&lt;/code&gt;&amp;nbsp;listo para pegar. Tiene una condición de entrada de la que hablo más adelante, porque cambia algo importante de lo que sigue.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;check_contrast&lt;/code&gt;: un chequeo de contraste libre entre dos hex cualquiera, sin depender de una paleta generada. Esta escala es monocromática por diseño (un solo tono, variando su luminosidad) y eso es justo lo que la hace matemáticamente predecible. Pero casi ninguna interfaz real vive solo de eso: un estado de error, una etiqueta de oferta, un acento de marca, necesitan un color que la escala monocromática no tiene.&amp;nbsp;&lt;code&gt;check_contrast&lt;/code&gt;&amp;nbsp;existe para que esa decisión también pase por la aritmética, en vez de por el ojo del modelo. Vuelvo más adelante sobre un problema que esto no resuelve del todo.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Un recurso,&amp;nbsp;&lt;code&gt;palette://{hex}/{theme}&lt;/code&gt;, que permite al agente extraer la matriz completa por URI antes de tomar una sola decisión de color.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Esto es exactamente el tipo de problema que conviene resolver con una herramienta en lugar de con el modelo, y la razón es precisa: el contraste es aritmética. Calcular la relación de luminancia entre dos valores hex es un cálculo, y los cálculos son el tipo de trabajo que conviene sacar de un sistema que predice texto. La idea completa es sacar esas matemáticas de la "cabeza" probabilística del modelo y ponerlas en una herramienta que devuelve una respuesta determinista.&lt;/p&gt;

&lt;p&gt;Esa fue la apuesta. Exponer la librería sobre MCP, y los fallos de contraste deberían volverse casi imposibles.&lt;/p&gt;

&lt;h3&gt;
  
  
  El diagnóstico: por qué el modelo ignora las reglas
&lt;/h3&gt;

&lt;p&gt;No se volvieron imposibles, aunque el modelo nunca ignoró la paleta. Llamaba a la herramienta, recibía la salida con cada color y cada regla intacta, y hacía algo más persistente: reescribía el CSS desde cero, con sus propias convenciones, sus propios nombres de variable, su propia estructura. Y, de forma crucial, sin las reglas de compatibilidad (el objetivo completo de la herramienta) que se perdían en algún punto entre leer el output y escribir la página.&lt;/p&gt;

&lt;p&gt;Esto no era constante ni catastrófico. Los fallos eran minoría, pero persistentes, y siempre tenían la misma forma: la información de emparejamiento seguro, la razón de ser de la herramienta, se evaporaba en el viaje desde la salida de la herramienta hasta la hoja de estilos final.&lt;/p&gt;

&lt;p&gt;¿Por qué pasa esto? No es un fallo de seguimiento de instrucciones. Es algo más simple y más difícil de combatir: un modelo entrenado con una cantidad enorme de CSS tiene un sentido muy fuerte de cómo se ve el CSS bien escrito, y cuando recibe un dato con forma de CSS, no lo copia, lo regenera con esa forma aprendida. Las anotaciones de accesibilidad nunca formaron parte de esa forma aprendida, así que no sobreviven a la regeneración.&lt;br&gt;
Generalmente, el System Prompt tiene un peso jerárquico mayor porque define el "personaje" y las reglas operativas globales del modelo. Sin embargo, incluso un prompt muy fuerte podría perder contra el sesgo de exposición (los ejemplos que el propio modelo acaba de generar) o el sesgo de entrenamiento mencionado anteriormente.&lt;/p&gt;

&lt;p&gt;El patrón es reconocible: el modelo conserva con fiabilidad las partes de la salida que no puede reconstruir de memoria (un hex concreto) y reescribe las partes que sí puede: la estructura, los nombres de las variables, el formato del comentario. No hay desobediencia en eso. Es la ruta de menor resistencia para un sistema que genera texto token a token, y la ruta de menor resistencia es siempre el CSS que ya ha visto millones de veces. Y ninguna cantidad de "IMPORTANTE: no reescribas esto" la vence de forma fiable, porque la instrucción es solo texto, y compite contra el sentido completo que el modelo tiene del medio.&lt;/p&gt;

&lt;p&gt;Esto reordena el problema: si la instrucción pierde casi siempre contra el prior de entrenamiento, la única palanca que queda no es pedirle al modelo que se comporte de otra manera. Es cambiar el formato de lo que recibe, para que la opción correcta sea más barata de copiar que de reescribir.&lt;/p&gt;
&lt;h3&gt;
  
  
  Tres formas de investigar esto sin ser investigadora de IA
&lt;/h3&gt;

&lt;p&gt;Una parte del proceso que no quiero ocultar: no soy investigadora de IA, y no entiendo los mecanismos internos de un transformador al nivel de los papers científicos. Pero eso no impide razonar bien sobre cómo se comporta uno, y lo hice apoyándome en tres fuentes distintas, cada una con un punto ciego que las otras dos no tenían.&lt;/p&gt;

&lt;p&gt;La primera fue un NotebookLM cargado con una docena de papers sobre LLMs, usado como un oráculo al que hacerle preguntas en lenguaje natural: por qué un modelo ignora instrucciones explícitas pero copia ciertos formatos, qué hace que un fragmento de texto sea más fácil de regenerar que de copiar. De ahí salió la hipótesis que finalmente probé: que la diferencia entre un comentario de bloque y uno en línea no es cosmética, sino que cambia cuánto le cuesta al modelo separar el dato de la estructura que lo rodea. &lt;br&gt;
Ese mismo ir y venir con el oráculo también produjo algo más estructural: una jerarquía de prioridades que explica por qué el cumplimiento de reglas lógicas puede perder de forma consistente contra los sesgos estadísticos del entrenamiento. Ese modelo mental es lo que me permitió convertir mis intentos fallidos en la arquitectura sobre la que descansa el proyecto hoy:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Nivel&lt;/th&gt;
&lt;th&gt;Componente&lt;/th&gt;
&lt;th&gt;Peso / Naturaleza&lt;/th&gt;
&lt;th&gt;Interacción con el Modelo&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Prior de Entrenamiento&lt;/td&gt;
&lt;td&gt;El más alto. El "tirón gravitacional" estadístico.&lt;/td&gt;
&lt;td&gt;Gana por defecto. Los modelos prefieren regenerar formas familiares aprendidas de miles de millones de tokens antes que copiar reglas a medida que se sienten "antinaturales" frente a su entrenamiento.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;System Prompt&lt;/td&gt;
&lt;td&gt;Alto (Estructural). Define la persona y los límites operativos.&lt;/td&gt;
&lt;td&gt;Marca el escenario, pero es texto. Puede ser anulado por el Prior u olvidado en ventanas de contexto largas ("Lost in the Middle").&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Tools / MCP&lt;/td&gt;
&lt;td&gt;Medio (Lógico). Funciones externas para lógica determinista.&lt;/td&gt;
&lt;td&gt;El modelo trata la salida de la herramienta como "más texto" sobre el que improvisar. El propio marco del oráculo para vencer al Prior aquí es Read-Plan-Generate. Forzar al modelo a planear y validar antes de generar. Funciona, pero solo cuando algo realmente impone la secuencia; dejado como sugerencia, el modelo salta directo a generar.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Skills&lt;/td&gt;
&lt;td&gt;Variable. Capacidades aprendidas (por ejemplo, código, matemáticas).&lt;/td&gt;
&lt;td&gt;¿Son system prompts? Por lo general no. Las skills son capacidades incorporadas en los pesos mediante SFT/RLHF. Invocas una skill a través de un prompt, pero la skill en sí forma parte del repertorio interno del modelo.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;La segunda fue el modelo implementador, en sesiones de trabajo reales: proponía formatos concretos y, por lo que preservaba frente a lo que eliminaba al generar código, revelaba cuáles de esos formatos resistían de verdad la regeneración.&lt;/p&gt;

&lt;p&gt;La tercera fue la más directa: preguntarles a los propios agentes, después de que generasen algo, por qué habían hecho lo que hicieron. No doy por buena esa fuente sin matizarla. Un modelo explicando su propio comportamiento es, en el mejor de los casos, una reconstrucción plausible, no introspección real; pero más de una vez esas respuestas señalaron, con sus propias palabras, exactamente el punto donde la herramienta estaba fallando. Una de ellas terminó proponiendo dos arreglos arquitectónicos razonables. Ninguna de las tres fuentes por sí sola habría llegado hasta aquí.&lt;/p&gt;
&lt;h3&gt;
  
  
  Primera solución: comentarios en línea
&lt;/h3&gt;

&lt;p&gt;Una vez que el diagnóstico estaba claro, la primera solución fue una cuestión de formato, no de instrucción.&lt;/p&gt;

&lt;p&gt;Un comentario de bloque al principio del archivo es, para el modelo, documentación separable: se elimina sin coste, porque quitar un bloque entero no rompe nada alrededor. Un comentario en línea, pegado al final de una declaración, es distinto: para eliminarlo, el modelo tiene que editar esa línea concreta, una por una, en lugar de descartar un bloque completo de una vez. Esa fricción adicional es suficiente para que, la mayoría de las veces, no se moleste en hacerlo.&lt;/p&gt;

&lt;p&gt;El formato ganador fue este:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nd"&gt;:root&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="py"&gt;--color-100&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#faf2f5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* ✅ text→900·800·700  ⚠️ lg→600 */&lt;/span&gt;
    &lt;span class="py"&gt;--color-300&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#e9bfcc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* ✅ text→900·800  ⚠️ lg→700 */&lt;/span&gt;
    &lt;span class="py"&gt;--color-600&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#c86f90&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* ✅ text→900  ⚠️ lg→100·800·white */&lt;/span&gt;
    &lt;span class="py"&gt;--color-700&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#bb4268&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* ✅ text→white·100  ⚠️ lg→300·900 */&lt;/span&gt;
    &lt;span class="py"&gt;--color-800&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#67273f&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* ✅ text→white·100·300  ⚠️ lg→600 */&lt;/span&gt;
    &lt;span class="py"&gt;--color-900&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#3b1521&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* ✅ text→white·100·300·600  ⚠️ lg→700 */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cada variable lleva soldada, como comentario en línea, la lista exacta de fondos sobre los que es segura. El modelo copia el bloque entero,&amp;nbsp;&lt;code&gt;:root {}&lt;/code&gt;&amp;nbsp;incluido, porque separar la regla de la variable cuesta más que copiar las dos juntas.&lt;br&gt;
Al poner el comentario inline al final de la variable, se está creando un vínculo sintáctico (Contextual Binding). Para el modelo, el token de la variable CSS y el token de la regla de accesibilidad están tan cerca en el espacio de atención que separarlos requeriría "nadar contra corriente" de su entrenamiento previo. El comentario se procesa como parte de la unidad lógica de la declaración.&lt;/p&gt;

&lt;p&gt;Llegar aquí costó varios intentos fallidos, y cada uno enseñó algo. Un aviso en mayúsculas dentro de un comentario ("COPIAR TAL CUAL") no cambió nada: un comentario que ordena copiarse sigue siendo, para el modelo, un comentario de una herramienta que no tiene capacidad real de forzar al modelo. Una tabla en texto plano antes del CSS se leía y se ignoraba igual, porque el modelo escribía su propio CSS de todas formas. El formato que funcionó no fue el más legible para un humano: fue el que más se parecía a lo que el modelo ya quería escribir.&lt;/p&gt;

&lt;h3&gt;
  
  
  Segunda intento: el flujo de planificación forzada
&lt;/h3&gt;

&lt;p&gt;Los comentarios en línea resuelven qué sobrevive una vez que el modelo tiene el CSS en la mano. No resuelven si el modelo llegó a esa CSS habiendo comprobado los pares antes de escribir nada. Para eso construí&amp;nbsp;&lt;code&gt;plan-palette-usage&lt;/code&gt;, un prompt de MCP que estructura la secuencia completa en cuatro pasos: leer la matriz, planificar en texto plano cada par antes de escribir CSS, validar esa lista con&amp;nbsp;&lt;code&gt;validate_pairings&lt;/code&gt;, y solo generar los tokens si la validación entera vino limpia.&lt;/p&gt;

&lt;p&gt;La idea era convertir un problema de criterio, que los modelos manejan de forma inconsistente, en un problema de ejecución de pasos, que manejan mejor. Funcionaba, cuando se invocaba. El problema, que tardé en ver con claridad, estaba en esa condición.&lt;/p&gt;

&lt;h3&gt;
  
  
  El matiz que cambió todo: los prompts necesitan un humano
&lt;/h3&gt;

&lt;p&gt;Asumí que el modelo podía activar&amp;nbsp;&lt;code&gt;plan-palette-usage&lt;/code&gt;&amp;nbsp;por su cuenta, igual que activa cualquier otra herramienta cuando le conviene. Eso es falso, y no por una limitación de tal o cual cliente: es así por especificación.&lt;/p&gt;

&lt;p&gt;De las tres primitivas de MCP, los &lt;strong&gt;tools&lt;/strong&gt; son &lt;em&gt;model-controlled&lt;/em&gt;: el modelo decide cuándo llamarlos. Los &lt;strong&gt;prompts&lt;/strong&gt; son &lt;em&gt;user-controlled&lt;/em&gt;: solo se disparan si una persona los invoca explícitamente. Lo que cambia entre clientes es la forma de esa invocación, no quién la dispara. En Claude Code, "explícitamente" significa literal: hace falta escribir el comando exacto, &lt;code&gt;/mcp:accessible-palette:plan-palette-usage&lt;/code&gt;, autocompletado incluido; pedirle al modelo en una instrucción en lenguaje natural que "use el prompt X" no alcanza. En Cline sí alcanza. Si en mi propio mensaje le pido que ejecute &lt;code&gt;plan-palette-usage&lt;/code&gt;, lo invoca sin que yo escriba el comando; pero la invocación sigue partiendo de que una persona lo pidió por su nombre o su intención, no de que el modelo decidiera por su cuenta que convenía usarlo. La barrera que importa para esta historia no es el formato del comando: es que en ningún cliente el modelo lo activa solo, sin que nadie se lo haya pedido.&lt;/p&gt;

&lt;p&gt;En la práctica, esto significa que el flujo de cuatro pasos casi nunca se ejecuta. Nadie le va a enseñar a alguien sin perfil técnico ni el comando exacto ni la frase necesaria para disparar un prompt guiado al pedir una landing page con un "haz una página sobre X usando el palette mcp". Y la garantía de accesibilidad de esta librería no podía depender de eso.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tercera solución: el gate en el servidor
&lt;/h3&gt;

&lt;p&gt;Esto fue lo que realmente me hizo cambiar de capa: si el modelo no puede invocar el prompt por sí mismo, entonces toda la garantía descansaba en un mecanismo que, en el uso real, casi nunca se activa. Necesitaba algo que no dependiera de que nadie escribiera un comando.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;validate_pairings&lt;/code&gt;&amp;nbsp;siempre calculó el contraste de forma determinista; eso nunca fue el punto débil. El punto débil era que&amp;nbsp;&lt;code&gt;generate_css_tokens&lt;/code&gt;, el tool que entrega el CSS de verdad, no sabía nada de lo que había pasado antes. Se podía saltar la validación entera y pedir los tokens igual, y el servidor los entregaba sin preguntar.&amp;nbsp;&lt;code&gt;proceed: false&lt;/code&gt;&amp;nbsp;era una convención de buena fe entre el prompt y el modelo; el servidor no tenía memoria de si esa convención se había respetado.&lt;/p&gt;

&lt;p&gt;La corrección, en código, fue pequeña: el servidor ahora lleva un registro de qué combinaciones de hex y tema ya pasaron&amp;nbsp;&lt;code&gt;validate_pairings&lt;/code&gt;&amp;nbsp;con éxito, y&amp;nbsp;&lt;code&gt;generate_css_tokens&lt;/code&gt;&amp;nbsp;consulta ese registro antes de generar nada. Si la combinación no está validada, el tool lanza un error y se niega a responder, de la misma forma en que falla si le pasas un hex inválido, no como un aviso que el modelo puede leer o no.&lt;/p&gt;

&lt;p&gt;Técnicamente, ese registro es un&amp;nbsp;&lt;code&gt;Set&lt;/code&gt;&amp;nbsp;en memoria, instanciado una sola vez cuando arranca el proceso de Node. El transporte es&amp;nbsp;&lt;code&gt;StdioServerTransport&lt;/code&gt;, así que cada cliente que lanza el servidor hace nacer un proceso nuevo, y "sesión" no significa nada más sofisticado que eso: el tiempo de vida de ese proceso. No persiste en disco, no tiene TTL. Es deliberadamente la unidad más simple que podía funcionar, y crucialmente, no depende de que nadie escriba nada a mano: vive en un tool que el modelo ya iba a llamar de todas formas.&lt;/p&gt;

&lt;p&gt;Lo que esto cambia es dónde vive la garantía. Antes dependía de que una persona supiera invocar un prompt, y de que el modelo respetara una convención de texto. Ahora no hay decisión posible: o la validación pasó antes, en este mismo proceso, para ese hex y ese tema, o el tool no produce salida.&lt;/p&gt;

&lt;p&gt;Sigo siendo honesta sobre el límite real: esto no impide que un agente escriba CSS a mano, ignorando la herramienta por completo, con los colores que le parezcan. Ningún servidor MCP puede impedir eso; vive fuera de su alcance. Lo que el gate garantiza es más estrecho y, a la vez, más sólido: si el agente usa esta herramienta para obtener los tokens, no hay forma de que los obtenga sin haber validado antes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cómo terminó todo
&lt;/h3&gt;

&lt;p&gt;Lo que sostiene el sistema hoy es, en esencia, una sola pieza: el gate del servidor, apoyado en los comentarios en línea para que lo validado no se pierda al copiarlo. El prompt sigue existiendo, sigue siendo invocable por quien conozca el comando, pero no tengo claro qué ventaja real le queda sobre el gate. En teoría fuerza una relectura de la matriz y un razonamiento explícito antes de validar; en la práctica, no tengo evidencia de que eso cambie el resultado final de un modo que el gate, por sí solo, no consiga ya. Lo dejo documentado como lo que es: un añadido opcional, no el mecanismo del que depende nada importante.&lt;/p&gt;

&lt;p&gt;Quiero ser concreta sobre qué significa "funciona", en vez de quedarme en "lo he usado y ha ido bien". Generé 45 páginas de demostración con varios agentes y modelos distintos corriendo sobre los agentes Open Code, Claude Code, Github Copilot y Cline, usando solo prompts mínimos en lenguaje natural, del tipo "haz una landing sobre X usando el palette mcp", sin invocar nunca el prompt guiado, y combinando esto con la skill de accesibilidad de&amp;nbsp;&lt;code&gt;github/awesome-copilot&lt;/code&gt;&amp;nbsp;(la misma que su autor, Michael Fairchild, afinó contra su propio harness de evaluación antes de publicarla). Le pasé&amp;nbsp;&lt;a href="https://github.com/dequelabs/axe-core" rel="noopener noreferrer"&gt;axe-core&lt;/a&gt;&amp;nbsp;a las 45:&amp;nbsp;&lt;strong&gt;35 no tuvieron ninguna violación de ningún tipo&lt;/strong&gt;. De las 10 que sí, 8 fueron específicamente de contraste de color; el problema exacto que esta librería existe para resolver, todavía colándose, pero en una fracción pequeña del total. Quiero señalar que la muestra es extremadamente pequeña para obtener ninguna estadística. Les animo a ponerlo a prueba. Las capturas y el detalle completo están en el repositorio, en&amp;nbsp;&lt;a href="https://github.com/ceciCoding/accessible-monochrome-palette/tree/main/demo" rel="noopener noreferrer"&gt;/demo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy4prh94w2rhhq5q32wzr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy4prh94w2rhhq5q32wzr.png" alt="Una de las muestras de demo. Una web para un campamento de verano con tonos verdes y amarillos" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;No siempre sale perfecto: de vez en cuando se cuela un fallo. Ya no en los tokens que el servidor entrega, que están cerrados matemáticamente, sino en el CSS de componente final, donde el modelo todavía puede ignorar el manifiesto si decide escribir a mano por fuera de la herramienta, o en los colores de acento, donde no tengo todavía el mismo tipo de garantía. El comportamiento de los modelos de lenguaje es probabilístico, y ningún parche (ni siquiera uno que vive en el servidor) es hermético contra un prior de formato que actúa por fuera del protocolo.&lt;/p&gt;

&lt;p&gt;No voy a poner un número de benchmark propio encima de esto más allá de lo que ya he compartido: la accesibilidad depende del contexto, tu modelo, tus prompts, tu mezcla de tareas mueven el resultado. Así que te invito directamente:&amp;nbsp;&lt;strong&gt;instala el servidor, pruébalo contra tus propios flujos de trabajo&lt;/strong&gt;, corre tu propio axe-core o Lighthouse contra lo que te genere, y cuéntame qué te da. Si encuentras un caso donde el modelo se sale del carril, o un formato que funciona mejor que el mío, quiero saberlo, es la única forma de seguir afinando esto.&lt;/p&gt;

&lt;p&gt;Lo que sí defiendo sin reservas es la dirección. Un puñado de fallos ocasionales, ahora acotados a la parte del proceso que el protocolo no puede tocar, vive en un mundo distinto al de "texto de bajo contraste en el 84% de las páginas" o a una tasa de aprobación no guiada del 12%. No estás instalando una restricción dura sobre todo el proceso; estás instalando una restricción dura sobre la parte aritmética, e inclinando con fuerza, con texto y con estructura, la parte que sigue siendo probabilística.&lt;/p&gt;

&lt;h3&gt;
  
  
  Qué viene después
&lt;/h3&gt;

&lt;p&gt;Quedan dos cosas abiertas, y prefiero nombrarlas con la misma honestidad que el resto del artículo en vez de dar la sensación de que aquí ya no hay nada que arreglar.&lt;/p&gt;

&lt;p&gt;La primera es mía: encontrar para los colores de acento algo equivalente a lo que los comentarios en línea logran para la paleta base, de forma que un acento validado con&amp;nbsp;&lt;code&gt;check_contrast&lt;/code&gt;&amp;nbsp;quede de algún modo soldado al CSS, en vez de perder esa validación en la primera edición.&lt;/p&gt;

&lt;p&gt;La segunda es de Marta: está trabajando en adaptar el algoritmo original a APCA (Accessible Perceptual Contrast Algorithm), el método de contraste perceptual que se está definiendo para WCAG 3.0.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;El algoritmo de la paleta fue creado por&amp;nbsp;&lt;a href="https://www.linkedin.com/in/hollyheh/" rel="noopener noreferrer"&gt;Marta Herrera Hollingsworth&lt;/a&gt;; la librería y el servidor MCP son míos. Esta pieza es un acompañamiento práctico de mi ensayo anterior, "As We May Code: Why Software Is a Human Problem Dressed in Logic".&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Datos de motivación: &lt;a href="//webaim.org/projects/million"&gt;WebAIM Million 2026&lt;/a&gt; y el informe &lt;a href="//microsoft.github.io/a11y-llm-eval-report"&gt;A11y LLM Eval&lt;/a&gt; de Michael Fairchild.&lt;/em&gt;&lt;br&gt;
&lt;em&gt;Formo parte del &lt;a href="https://accessibility.github.com/accessibility-advisory-panel.html" rel="noopener noreferrer"&gt;GitHub Accessibility Advisory Panel&lt;/a&gt;; fue ahí donde conocí el trabajo de Michael Fairchild que motiva este proyecto.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>a11y</category>
      <category>mcp</category>
      <category>spanish</category>
    </item>
    <item>
      <title>As We May Code: Why Software Is a Human Problem Dressed in Logic</title>
      <dc:creator>Ceci Olivera</dc:creator>
      <pubDate>Thu, 16 Apr 2026 21:29:21 +0000</pubDate>
      <link>https://dev.to/cec1_c0d/as-we-may-code-why-software-is-a-human-problem-dressed-in-logic-338m</link>
      <guid>https://dev.to/cec1_c0d/as-we-may-code-why-software-is-a-human-problem-dressed-in-logic-338m</guid>
      <description>&lt;p&gt;&lt;em&gt;On interfaces, accessibility, wicked problems, and the illusion that machines can write code for us.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;The first time I used NotebookLM, I fed it an essay I'd been meaning to read for months: Vannevar Bush's "As We May Think," published in &lt;em&gt;The Atlantic&lt;/em&gt; in 1945. I knew it was the essay that predicted the internet. What I didn't know was that it was about something much more fundamental.&lt;/p&gt;

&lt;p&gt;Bush's central concern was that humanity generates knowledge faster than any individual can absorb it — that the collective output of science, culture, and experience had outgrown the tools we had for navigating it. He proposed a machine called the Memex to help: not a machine that thinks for us, but one that helps us think. A tool for making associative trails through the ever-expanding record of human knowledge, mirroring the way thought actually moves — by meaning, by context, not by alphabetical index.&lt;/p&gt;

&lt;p&gt;The synchronicity wasn't lost on me. I was using an AI tool to navigate a piece of that very record, to absorb an essay I hadn't found the time to read. And the tool worked. It helped me absorb the essay faster, make connections, follow the thread. It was doing, in a small way, what Bush imagined eighty years ago: amplifying my thought, not replacing it.&lt;/p&gt;

&lt;p&gt;But here's what stayed with me. Bush wasn't just predicting a technology. He was drawing a line. On one side: technology that extends what humans can do. On the other: technology that substitutes for what humans do. He was firmly, explicitly, on the side of amplification.&lt;/p&gt;

&lt;p&gt;That line matters now more than ever. Because there is a quiet assumption that most people hold about software: that it is, fundamentally, a problem of logic. You define what you want, you write the rules, the machine executes them. Input, process, output. Deterministic. Clean.&lt;/p&gt;

&lt;p&gt;This assumption is wrong. And it is the root of a much larger misunderstanding — one that shapes how we think about AI-generated code, about the role of developers, and about what it actually means to build something that people use.&lt;/p&gt;

&lt;h2&gt;
  
  
  I. The deterministic illusion
&lt;/h2&gt;

&lt;p&gt;Software runs on deterministic machines. Given the same input, a CPU will produce the same output, every single time. This is a fact about execution, and it is what makes computers useful.&lt;/p&gt;

&lt;p&gt;But somewhere along the way, we confused the nature of &lt;em&gt;running&lt;/em&gt; software with the nature of &lt;em&gt;creating&lt;/em&gt; it. Because the machine is deterministic, we assumed that building software must also be a deterministic process: define the requirements, translate them into code, compile, ship. A pipeline. A factory.&lt;/p&gt;

&lt;p&gt;Fred Brooks dismantled this illusion in 1986. In "No Silver Bullet," he distinguished between two kinds of complexity. Accidental complexity is what we create ourselves — the friction of our tools, the limitations of our languages, the overhead of our processes. This is the complexity that better tools can eliminate. Essential complexity is something else entirely. It comes from the problem itself: from what the software needs to do, for whom, in what context, under what constraints. No tool can remove it, because it is not an artifact of the process — it is the substance of it.&lt;/p&gt;

&lt;p&gt;Brooks's claim was radical and, forty years later, still widely misunderstood: the hard part of software is not writing code. The hard part is deciding what to build. And that decision is irreducibly human.&lt;/p&gt;

&lt;p&gt;In 1992, Jack Reeves took this further with an essay that should be required reading for anyone who writes code. In "What Is Software Design?", he argued that source code is not the construction of software — it is the &lt;em&gt;design&lt;/em&gt;. Compiling is construction: it's cheap, it's automatic, you press a button. But the act of writing code? That is a design activity. It is creative, contextual, full of trade-offs and judgment calls. It is, in the deepest sense, a human endeavor.&lt;/p&gt;

&lt;p&gt;If code is design, then the developer is not a technician executing a blueprint. The developer is a designer making decisions that directly shape how people experience a product. Every line of code carries assumptions about who will use it, how, and why.&lt;/p&gt;

&lt;h2&gt;
  
  
  II. Interfaces are wicked problems
&lt;/h2&gt;

&lt;p&gt;In 1973, Horst Rittel and Melvin Webber published a paper that gave vocabulary to something designers and planners had always felt but could never quite name. They called it "wicked problems" — not wicked in the moral sense, but in the sense of being deeply resistant to the kind of linear, analytical problem-solving that works so well in engineering and mathematics.&lt;/p&gt;

&lt;p&gt;Wicked problems have no definitive formulation. They have no stopping rule — no signal that tells you the problem is solved. Their solutions are not true or false, only better or worse. Every attempt at a solution changes the problem itself. And they are essentially unique: what works in one context may fail in another.&lt;/p&gt;

&lt;p&gt;User interfaces are wicked problems. Not metaphorically — structurally. Consider:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;There is no single correct interface for any given task. The "right" design depends on the user, their device, their abilities, their context, their mental model, their culture, their patience, their internet connection.&lt;/li&gt;
&lt;li&gt;Interfaces are never finished. Every new feature, every new user, every new device introduces new interactions, new edge cases, new failures.&lt;/li&gt;
&lt;li&gt;You cannot test your way to a perfect interface. You can only test your way to a &lt;em&gt;less wrong&lt;/em&gt; one, because the space of human variability is infinite.&lt;/li&gt;
&lt;li&gt;Every design decision carries consequences that ripple outward. A color choice excludes people with low vision. A navigation pattern confuses people using screen readers. A gesture-based interaction locks out people with motor disabilities. A modal dialog that "works" for sighted users traps keyboard users in an invisible cage — or, conversely, lets their focus escape into a background that is supposed to be inert.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Richard Buchanan connected wicked problems to design thinking in 1992, arguing that what many people dismiss as "impossible" may actually be a failure of imagination — one that better design thinking can overcome. Don Norman, through decades of work from &lt;em&gt;The Design of Everyday Things&lt;/em&gt; to &lt;em&gt;Living with Complexity&lt;/em&gt;, showed that the complexity of our tools must match the complexity of our lives. Simplifying an interface beyond a certain point doesn't make it better — it makes it dishonest about the problem it's trying to solve.&lt;/p&gt;

&lt;p&gt;The temptation, always, is to treat interfaces as tame problems. Define the requirements. Implement the spec. Check the boxes. Ship. But the users were never part of the spec, not really — not in all their unpredictable, embodied, diverse humanity.&lt;/p&gt;

&lt;h2&gt;
  
  
  III. HTML: the most human language in code
&lt;/h2&gt;

&lt;p&gt;Here is something people rarely say about HTML: it is beautiful.&lt;/p&gt;

&lt;p&gt;Not in the way that an elegant algorithm is beautiful, or a well-factored function. HTML is beautiful because it is a language of &lt;em&gt;meaning&lt;/em&gt;. It is a declarative API — arguably the most successful one ever created — whose purpose is not to instruct a machine but to communicate intent.&lt;/p&gt;

&lt;p&gt;When you write &lt;code&gt;&amp;lt;nav&amp;gt;&lt;/code&gt;, you are not telling the browser to render a box. You are saying: this is navigation. When you write &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt;, you are not just creating a clickable element — you are declaring that this element is interactive, that it can receive focus, that it responds to keyboard events, that it announces itself to assistive technology as something a person can press. A &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; carries decades of human expectations in six characters.&lt;/p&gt;

&lt;p&gt;HTML is not a programming language in the classical sense. It has no conditionals, no loops, no variables. And yet it may be the most consequential language on the web, because it is the layer where machine logic meets human meaning. It is the interface between the accessibility tree and the person. Between the browser and the body.&lt;/p&gt;

&lt;p&gt;And it is also, without question, the most widely written and most widely written &lt;em&gt;poorly&lt;/em&gt; language on the web. Because it is forgiving. Because a &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; with an &lt;code&gt;onclick&lt;/code&gt; looks the same as a &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; — to sighted users, on a good day, with a mouse. The damage is invisible until someone tries to navigate with a keyboard, or a screen reader, or a switch device. And by then, the developer has moved on.&lt;/p&gt;

&lt;p&gt;This is the paradox: the language that carries the most human meaning is treated as though it carries the least. Because it "works." Because it renders. Because the tests pass.&lt;/p&gt;

&lt;h2&gt;
  
  
  IV. What the machines see
&lt;/h2&gt;

&lt;p&gt;In 2024, Michael Fairchild at Microsoft built a tool to answer a simple question: when you ask an LLM to generate HTML for common UI components, how accessible is the result?&lt;/p&gt;

&lt;p&gt;The answer, documented in the A11y LLM Eval project, is sobering. Without explicit accessibility instructions, most models score near zero. The average across all tested models is around 10%. Even the best-performing model only passes 41% of tests. And these are not obscure edge cases — they are fundamental patterns like forms, navigation, dialogs, and data tables.&lt;/p&gt;

&lt;p&gt;An important caveat: these percentages do not mean "41% accessible according to WCAG." The tool evaluates code against axe-core (an automated accessibility scanner) plus a set of custom assertions per component. But automated tools can only catch a fraction of real accessibility barriers — perhaps 30-40% of WCAG criteria. Everything that requires human judgment — whether the reading order makes sense, whether an alt text is actually meaningful, whether a focus management pattern is appropriate for the context, whether the right ARIA design pattern was chosen — is beyond what any scanner can measure. So when we say "90% pass rate with good instructions," we mean 90% of the &lt;em&gt;automatable&lt;/em&gt; tests pass. The full picture still requires a human who understands what accessible means.&lt;/p&gt;

&lt;p&gt;Fairchild is explicit about this: these tests do not fully evaluate WCAG or guarantee accessible results. Manual testing remains essential.&lt;/p&gt;

&lt;p&gt;With that caveat in mind, the results are still revealing. Why do models perform so poorly by default? Because approximately 95% of websites have accessibility issues. The training data is the web, and the web is broken. LLMs don't learn what HTML &lt;em&gt;should&lt;/em&gt; be — they learn what HTML &lt;em&gt;is&lt;/em&gt;, statistically. And statistically, it's &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; soup.&lt;/p&gt;

&lt;p&gt;But Fairchild's research reveals something even more interesting. When you add explicit accessibility instructions — custom instruction files that tell the model to use semantic HTML, follow WCAG, ensure keyboard support — the automatable pass rates jump dramatically. A minimal instruction ("All output MUST be accessible") alone produced an 18 percentage point improvement. Detailed, expert-level guidance pushed some models from near-zero to over 90%.&lt;/p&gt;

&lt;p&gt;Think about what this means. The models &lt;em&gt;can&lt;/em&gt; generate code that passes automated checks. The knowledge is somewhere in the weights. But without a human explicitly asking for it — without someone who understands accessibility framing the problem — the model defaults to the statistical average. The inaccessible average. And even at its best, it still cannot evaluate the things that matter most: the human things, the contextual things, the wicked things.&lt;/p&gt;

&lt;p&gt;This is not a bug. This is the fundamental nature of what LLMs do: they index. They find the most probable pattern given the input. Vannevar Bush warned us about this in 1945 — that the artificiality of indexing systems would always be a poor substitute for the associative, contextual, meaning-driven way humans actually think. LLMs are the most sophisticated indexing system ever built. They are extraordinary at finding what is common. They are structurally incapable of understanding what is &lt;em&gt;right&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  V. The old tension, resurfaced
&lt;/h2&gt;

&lt;p&gt;There is a deeper reason why AI struggles with interfaces and accessibility, beyond training data. Interfaces are fundamentally human problems. They are wicked problems where context, intent, and user behavior all shape what the right solution looks like.&lt;/p&gt;

&lt;p&gt;A modal is not a modal because of its markup. It is a modal because of what it means to the user in that moment: stop, pay attention, deal with this before you continue. A &lt;code&gt;role="status"&lt;/code&gt; is not a technical annotation — it is a social contract: this information changed, and you should know, but I won't interrupt you. These are not pattern-matching problems. They are problems of human communication, encoded in HTML.&lt;/p&gt;

&lt;p&gt;AI approaches this as a statistical exercise: given this prompt, what code is most likely? But accessibility — and interface design more broadly — requires understanding &lt;em&gt;why&lt;/em&gt; a pattern exists, not just &lt;em&gt;what&lt;/em&gt; it looks like. It requires knowing that a person using a screen reader will navigate by headings, not linearly. That a person with low vision needs more than a change in opacity to perceive a state change. That a person with a motor disability needs every interactive element to be reachable by keyboard. That these are not edge cases — they are the full spectrum of how humans meet technology.&lt;/p&gt;

&lt;p&gt;Kat Holmes calls this the "mismatch" — the idea that disability is not in the person but in the gap between the person and the design. Sara Hendren goes further: nearly everything we build is assistive technology, designed to bridge the gap between body and world. The question is whether we acknowledge this or pretend the gap doesn't exist.&lt;/p&gt;

&lt;p&gt;What AI brings to the table is not a new problem. It is an old tension, resurfaced with new urgency: the temptation to reduce a human problem to pure logic. Herbert Simon dreamed of this in &lt;em&gt;The Sciences of the Artificial&lt;/em&gt; — a world where design could be formalized, optimized, computed. Fred Brooks answered him: the complexity of software is essential, not accidental. You cannot abstract it away without abstracting away its meaning.&lt;/p&gt;

&lt;p&gt;The illusion is seductive. If software were truly deterministic — if interfaces were tame problems with optimal solutions — then of course machines could write them better than we can. They are faster, cheaper, tireless. But interfaces are not tame. They are wicked. They involve bodies, contexts, cultures, disabilities, emotions, expectations. They involve &lt;em&gt;people&lt;/em&gt;. And people resist reduction to statistics.&lt;/p&gt;

&lt;h2&gt;
  
  
  VI. As we may code
&lt;/h2&gt;

&lt;p&gt;I started this essay describing a small moment: using an AI tool to read a text about the limits of human processing. The tool helped. It genuinely did. It compressed time, surfaced connections, let me absorb an 80-year-old essay in a fraction of the time it would have taken otherwise. Bush would have recognized it as a Memex — a machine that extends human thought.&lt;/p&gt;

&lt;p&gt;But the understanding that came from that essay — the realization that its argument about amplification versus substitution was directly relevant to my own work in accessibility and code — that was not something the tool provided. That was association. That was meaning. That was the intricate web of trails carried by the cells of a brain that has spent years thinking about HTML, about screen readers, about the gap between what we build and who we build it for.&lt;/p&gt;

&lt;p&gt;Eighty years after Bush, we have tools that can generate code at extraordinary speed. They can produce a login form in seconds, a dashboard in minutes, an entire landing page before you finish your coffee. But speed is not understanding. And production is not design.&lt;/p&gt;

&lt;p&gt;The question Bush asked is still open: does our technology amplify human thought, or does it replace it? With LLMs applied to code, we are in danger of choosing the second option — not by conscious decision, but by the quiet assumption that software is a deterministic problem that machines can solve. It isn't. It never has been.&lt;/p&gt;

&lt;p&gt;The developers, designers, and accessibility specialists who understand this are not being replaced by AI. They are becoming more essential. Because someone needs to write the custom instructions that turn a 0% automated pass rate into a 90% one — and then do the manual work that covers everything automation can't. Someone needs to know that a &lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; is not just a &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; with a backdrop. Someone needs to understand that the user on the other side of the screen might not see, might not hear, might not use a mouse, might not think the way the designer assumed — and that this is not an edge case but the human condition.&lt;/p&gt;

&lt;p&gt;HTML is a declarative API of meaning. The most successful one ever built. It was designed to be read by machines and understood by people. It is, in its quiet way, a bridge between logic and humanity. And the decision of which element to use — &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; or &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;nav&amp;gt;&lt;/code&gt; or &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; or &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; — is never a technical decision. It is a decision about who gets to participate.&lt;/p&gt;




&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Bush, V. (1945). "As We May Think." &lt;em&gt;The Atlantic Monthly&lt;/em&gt;, July 1945.&lt;/li&gt;
&lt;li&gt;Rittel, H.W. &amp;amp; Webber, M.M. (1973). "Dilemmas in a General Theory of Planning." &lt;em&gt;Policy Sciences&lt;/em&gt;, 4(2), 155–169.&lt;/li&gt;
&lt;li&gt;Brooks, F. (1986). "No Silver Bullet: Essence and Accident in Software Engineering." &lt;em&gt;Proceedings of the IFIP Tenth World Computing Conference.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Reeves, J.W. (1992). "What Is Software Design?" &lt;em&gt;The C++ Journal.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Buchanan, R. (1992). "Wicked Problems in Design Thinking." &lt;em&gt;Design Issues&lt;/em&gt;, 8(2), 5–21.&lt;/li&gt;
&lt;li&gt;Norman, D. (2013). &lt;em&gt;The Design of Everyday Things.&lt;/em&gt; Revised and expanded edition. Basic Books.&lt;/li&gt;
&lt;li&gt;Norman, D. (2010). &lt;em&gt;Living with Complexity.&lt;/em&gt; MIT Press.&lt;/li&gt;
&lt;li&gt;Simon, H. (1996). &lt;em&gt;The Sciences of the Artificial.&lt;/em&gt; 3rd edition. MIT Press.&lt;/li&gt;
&lt;li&gt;Holmes, K. (2018). &lt;em&gt;Mismatch: How Inclusion Shapes Design.&lt;/em&gt; MIT Press.&lt;/li&gt;
&lt;li&gt;Hendren, S. (2020). &lt;em&gt;What Can a Body Do? How We Meet the Built World.&lt;/em&gt; Riverhead Books.&lt;/li&gt;
&lt;li&gt;Fairchild, M. (2026). "Embedding Accessibility into AI-based Software Development." Tool: github.com/microsoft/a11y-llm-eval.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>a11y</category>
      <category>ai</category>
      <category>html</category>
      <category>programming</category>
    </item>
    <item>
      <title>Your Accessibility Overlay Is a Scam (And Here's the Proof)</title>
      <dc:creator>Ceci Olivera</dc:creator>
      <pubDate>Sun, 25 Jan 2026 18:46:44 +0000</pubDate>
      <link>https://dev.to/cec1_c0d/your-accessibility-overlay-is-a-scam-and-heres-the-proof-5g76</link>
      <guid>https://dev.to/cec1_c0d/your-accessibility-overlay-is-a-scam-and-heres-the-proof-5g76</guid>
      <description>&lt;p&gt;You've probably seen them: that little wheelchair icon floating in the corner of websites, promising "accessibility" with one click. Maybe your company even pays for one. Products like accessiBe, UserWay, EqualWeb, and AudioEye promise to make your website accessible and legally compliant by just adding a single line of JavaScript.&lt;/p&gt;

&lt;p&gt;It sounds too good to be true. &lt;strong&gt;Because it is.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let me be clear from the start: I'm not being hyperbolic when I say "scam." In January 2025, the &lt;a href="https://www.ftc.gov/legal-library/browse/cases-proceedings/2223156-accessibe-inc" rel="noopener noreferrer"&gt;Federal Trade Commission fined accessiBe $1 million&lt;/a&gt; for deceptive marketing claims. The FTC determined that accessiBe was falsely claiming their product made websites "WCAG compliant" when it simply couldn't deliver on that promise.&lt;/p&gt;

&lt;p&gt;And accessiBe isn't an outlier—it's the industry leader. The others make the same claims.&lt;/p&gt;

&lt;h2&gt;
  
  
  What accessibility overlays actually are
&lt;/h2&gt;

&lt;p&gt;An accessibility overlay is a JavaScript widget that sits on top of your website. When users click on it, they get a panel with options like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Change colors/contrast&lt;/li&gt;
&lt;li&gt;Increase font size&lt;/li&gt;
&lt;li&gt;Enable a "screen reader"&lt;/li&gt;
&lt;li&gt;Activate "profiles" for blindness, dyslexia, ADHD, etc.&lt;/li&gt;
&lt;li&gt;A magical toggle to "make the site WCAG compliant"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flakaw75hatgszr4kt2fa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flakaw75hatgszr4kt2fa.png" alt="View of an open accessibility overlay showing its features" width="524" height="897"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The pitch to businesses is irresistible: instead of spending money on actual accessibility work, just pay us $500-2000/year and we'll handle everything. Avoid lawsuits! Be compliant! One line of code!&lt;/p&gt;

&lt;h2&gt;
  
  
  Why they can't work (technically)
&lt;/h2&gt;

&lt;p&gt;Here's the thing: &lt;strong&gt;web accessibility isn't about surface-level appearance&lt;/strong&gt;. It's about the underlying structure of your HTML.&lt;/p&gt;

&lt;p&gt;WCAG (Web Content Accessibility Guidelines) is the international standard that accessibility laws reference. It has specific technical requirements like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Semantic HTML structure&lt;/strong&gt;: headings (&lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;h2&amp;gt;&lt;/code&gt;), lists, landmarks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Properly labeled form fields&lt;/strong&gt;: &lt;code&gt;&amp;lt;label&amp;gt;&lt;/code&gt; elements correctly associated with inputs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keyboard navigation&lt;/strong&gt;: logical tab order, focus management&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ARIA attributes&lt;/strong&gt;: proper roles, states, and properties for dynamic content&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Text alternatives&lt;/strong&gt;: meaningful alt text for images (not auto-generated garbage)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An overlay is JavaScript that runs &lt;em&gt;on top of&lt;/em&gt; your existing code. &lt;strong&gt;It cannot change your source code.&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;If your HTML uses &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; where it should use &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt;, the overlay can't fix that. If your form fields don't have proper labels, the overlay can't create that association. If your modal doesn't trap focus correctly, the overlay can't restructure your DOM.&lt;/p&gt;

&lt;p&gt;It's like putting a cardboard on a staircase and calling it a wheelchair ramp.&lt;/p&gt;

&lt;h2&gt;
  
  
  The features are either fake, redundant, or irrelevant
&lt;/h2&gt;

&lt;p&gt;Let's break down what these overlays actually offer:&lt;/p&gt;

&lt;h3&gt;
  
  
  Fake: "Make site WCAG compliant" toggle
&lt;/h3&gt;

&lt;p&gt;EqualWeb literally has an ON/OFF switch labeled "Make the site comply with WCAG."&lt;/p&gt;

&lt;p&gt;This is the exact claim the FTC sanctioned. WCAG 2.1 has &lt;strong&gt;78 success criteria&lt;/strong&gt;. Many require structural changes to your codebase. No JavaScript widget can magically fix all of them with a toggle. This feature is, to put it plainly, a lie.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fake: "Blindness profile"
&lt;/h3&gt;

&lt;p&gt;Blind users already have screen readers. Professional ones. JAWS, NVDA (free), VoiceOver (built into Apple devices), TalkBack (Android). They've spent years learning these tools and configuring them to their needs.&lt;/p&gt;

&lt;p&gt;An overlay that "adapts the site for blindness" can actually &lt;em&gt;interfere&lt;/em&gt; with their existing screen reader, change keyboard shortcuts they rely on, and create an inconsistent experience across websites.&lt;/p&gt;

&lt;p&gt;Moreover, I've checked that some of these overlay screenreaders ignore aria-hidden, the standard for removing a node from the accessibility tree (AOM), which is the data structure that dependable screen readers use to know what they are allowed to read.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fake: "AI-powered image descriptions"
&lt;/h3&gt;

&lt;p&gt;Overlays claim to automatically generate alt text for images using AI. The problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The descriptions are generic ("a person smiling") instead of contextual ("CEO presenting Q3 results")&lt;/li&gt;
&lt;li&gt;Decorative images should have &lt;code&gt;alt=""&lt;/code&gt; (empty), but AI adds unnecessary descriptions&lt;/li&gt;
&lt;li&gt;WCAG requires alt text to be &lt;em&gt;functionally equivalent&lt;/em&gt;, which requires human judgment about purpose&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Redundant: Everything else
&lt;/h3&gt;

&lt;p&gt;Most overlay features already exist at the operating system level:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Overlay feature&lt;/th&gt;
&lt;th&gt;Already exists in...&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Zoom/Magnification&lt;/td&gt;
&lt;td&gt;Windows Magnifier, macOS Zoom, browser Ctrl+/-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;High contrast&lt;/td&gt;
&lt;td&gt;Windows High Contrast, macOS Increase Contrast&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Color filters (colorblindness)&lt;/td&gt;
&lt;td&gt;Windows, macOS, iOS, Android color filters&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Screen reader&lt;/td&gt;
&lt;td&gt;NVDA (free), VoiceOver (free), TalkBack (free), Narrator (free)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Large cursor&lt;/td&gt;
&lt;td&gt;System accessibility settings on all OS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Voice control&lt;/td&gt;
&lt;td&gt;Dragon, Windows Voice Control, macOS Voice Control&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Text-to-speech&lt;/td&gt;
&lt;td&gt;Built into every modern OS and browser&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;On-screen keyboard&lt;/td&gt;
&lt;td&gt;Built into every OS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dictionary&lt;/td&gt;
&lt;td&gt;Built into every OS (Ctrl+Cmd+D on Mac, right-click on Windows)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;People who need these features &lt;strong&gt;already have them configured system-wide&lt;/strong&gt;, working across all apps and websites. They don't need each website to provide its own inferior version. Also, none of them are WCAG requirements and therefore, not a web dev responsibility.&lt;/p&gt;

&lt;h3&gt;
  
  
  Irrelevant: "Profiles" for ADHD, Dyslexia, Epilepsy, etc.
&lt;/h3&gt;

&lt;p&gt;WCAG doesn't have success criteria for "ADHD profile" or "dyslexia mode." These aren't legal requirements. &lt;/p&gt;

&lt;p&gt;What WCAG &lt;em&gt;does&lt;/em&gt; require is specific things like not having content that flashes more than 3 times per second (for seizure safety), proper text spacing, ability to pause moving content, etc. An overlay can't stop a video from flashing. An overlay can't fix your auto-playing carousel.&lt;/p&gt;

&lt;p&gt;These "profiles" are marketing theater — they sound inclusive but don't address actual legal compliance.&lt;/p&gt;

&lt;h2&gt;
  
  
  They don't protect you from lawsuits
&lt;/h2&gt;

&lt;p&gt;The whole pitch is "avoid lawsuits." But the data shows the opposite:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;25% of accessibility lawsuits in 2024 were against websites with overlays installed.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The legal precedent is clear. In the &lt;em&gt;Murphy v. Eyebobs&lt;/em&gt; case, the court explicitly rejected the argument that an overlay provided adequate accessibility. In the &lt;em&gt;LightHouse v. ADP&lt;/em&gt; settlement, the agreement explicitly stated that "overlay solutions such as those provided by AudioEye and accessiBe &lt;strong&gt;will not be sufficient&lt;/strong&gt; to achieve accessibility."&lt;/p&gt;

&lt;p&gt;Plaintiff attorneys &lt;em&gt;know&lt;/em&gt; about overlays. Finding an overlay on a website doesn't make them go away—it shows them you tried to take a shortcut instead of doing the actual work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Over 1000 accessibility professionals have signed against them
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://overlayfactsheet.com" rel="noopener noreferrer"&gt;Overlay Fact Sheet&lt;/a&gt; is a public document signed by more than 1000 accessibility professionals worldwide, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Contributors to WCAG, ARIA, and HTML standards&lt;/li&gt;
&lt;li&gt;Employees at Google, Microsoft, Apple, BBC&lt;/li&gt;
&lt;li&gt;Independent accessibility consultants&lt;/li&gt;
&lt;li&gt;Disability advocates&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The statement is clear: overlays don't work, they often make things worse, and they represent a misallocation of resources that should go toward real accessibility.&lt;/p&gt;

&lt;p&gt;accessiBe, UserWay, EqualWeb, and AudioEye are all explicitly named.&lt;/p&gt;

&lt;h2&gt;
  
  
  The European Disability Forum has warned against them
&lt;/h2&gt;

&lt;p&gt;In May 2023, the &lt;a href="https://www.edf-feph.org/accessibility-overlays-dont-guarantee-compliance-with-european-legislation/" rel="noopener noreferrer"&gt;European Disability Forum (EDF) and the International Association of Accessibility Professionals (IAAP) issued a joint statement&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Overlays &lt;strong&gt;do not make the website accessible or compliant with European accessibility legislation&lt;/strong&gt;. They do not constitute an acceptable alternative or a substitute for fixing the website itself."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is particularly relevant now that the European Accessibility Act (EAA) is in effect as of June 2025.&lt;/p&gt;

&lt;h2&gt;
  
  
  They also introduce security risks
&lt;/h2&gt;

&lt;p&gt;In November 2022, &lt;a href="https://www.imperva.com/blog/vulnerability-discovered-in-equalweb-accessibility-widget/" rel="noopener noreferrer"&gt;Imperva's security research team discovered a DOM XSS vulnerability&lt;/a&gt; in EqualWeb's widget. The vulnerability affected major clients including Fiverr, Bosch, Playtika, and Avis.&lt;/p&gt;

&lt;p&gt;When you add a third-party JavaScript widget to your site, you're trusting that vendor with your users' security. These overlays load external scripts on every page load, expanding your attack surface.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you should do instead
&lt;/h2&gt;

&lt;p&gt;If you actually care about accessibility (and avoiding lawsuits):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Remove the overlay.&lt;/strong&gt; It's not helping. It might be hurting.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Get an audit.&lt;/strong&gt; Hire an accessibility professional to evaluate your site against WCAG 2.1 AA.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Fix the actual issues.&lt;/strong&gt; Work with your development team to address problems in the source code. This means semantic HTML, proper ARIA, keyboard navigation, color contrast, alt text, etc.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Train your team.&lt;/strong&gt; Designers, developers, and content creators should understand accessibility basics so new features don't introduce new barriers.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Test with real users.&lt;/strong&gt; Include people with disabilities in your QA process. They'll find issues automated tools miss.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Yes, this costs more than a few bucks a month. But it actually works. And it won't get you sued.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bottom line
&lt;/h2&gt;

&lt;p&gt;Accessibility overlays are selling you a lie: that you can buy compliance without doing the work. The FTC has called it deceptive. The disability community has rejected it. The data shows they don't prevent lawsuits.&lt;/p&gt;

&lt;p&gt;If your company uses one, share this article with your manager. If you're a developer being asked to implement one, push back with this evidence. If you're evaluating accessibility solutions, run away from anyone promising a one-line JavaScript fix.&lt;/p&gt;

&lt;p&gt;Real accessibility requires real work. There are no shortcuts.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Further reading:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://overlayfactsheet.com" rel="noopener noreferrer"&gt;Overlay Fact Sheet&lt;/a&gt; — The public statement signed by 1000+ professionals&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://overlayfalseclaims.com" rel="noopener noreferrer"&gt;Overlay False Claims&lt;/a&gt; — Documented evidence of misleading marketing&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.ftc.gov/legal-library/browse/cases-proceedings/2223156-accessibe-inc" rel="noopener noreferrer"&gt;FTC v. accessiBe&lt;/a&gt; — The $1M fine for deceptive practices&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.edf-feph.org/accessibility-overlays-dont-guarantee-compliance-with-european-legislation/" rel="noopener noreferrer"&gt;EDF/IAAP Joint Statement&lt;/a&gt; — European Disability Forum warning&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://adrianroselli.com" rel="noopener noreferrer"&gt;Adrian Roselli's blog&lt;/a&gt; — Extensive documentation of overlay problems&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.w3.org/WAI/WCAG21/quickref/" rel="noopener noreferrer"&gt;WCAG 2.1 Quick Reference&lt;/a&gt; — What actual compliance looks like&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>a11y</category>
      <category>automation</category>
    </item>
    <item>
      <title>¿Qué significa que Javascript está basado en prototipos?</title>
      <dc:creator>Ceci Olivera</dc:creator>
      <pubDate>Sat, 12 Sep 2020 09:21:05 +0000</pubDate>
      <link>https://dev.to/cec1_c0d/que-significa-que-javascript-esta-basado-en-prototipos-1g0a</link>
      <guid>https://dev.to/cec1_c0d/que-significa-que-javascript-esta-basado-en-prototipos-1g0a</guid>
      <description>&lt;p&gt;Cuando empecé a estudiar Javascript y POO escuché muchas veces que &lt;strong&gt;a pesar de que Javascript es un lenguaje orientado a objetos no está basado en clases sino en prototipos.&lt;/strong&gt;&lt;br&gt;
En este artículo intentaremos entender qué significa exactamente esto y por qué es importante saber qué hace un prototipo.&lt;/p&gt;

&lt;p&gt;En otros lenguajes orientados a objetos &lt;strong&gt;cuando declaramos una clase estamos creando un nuevo tipo de dato compuesto&lt;/strong&gt;, es decir, un tipo de dato formado por tipos primitivos. Pero esto no es lo que ocurre en Javascript, a pesar de que desde ES2015 usemos la palabra reservada class. &lt;strong&gt;Los prototipos son instancias de objetos.&lt;/strong&gt; Mientras que las clases son modelos, &lt;strong&gt;los objetos heredan directamente de otros objetos por defecto en JS.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Entonces, primero que nada tenemos que entender &lt;strong&gt;qué es la cadena de prototipos.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;La cadena de prototipos es una &lt;strong&gt;estructura en forma de árbol que conecta funcionalidades entre objetos y en cuya raíz se encuentra Object.prototype.&lt;/strong&gt; Object.prototype provee de una serie de métodos que aparecen en todos los objetos, como por ejemplo toString( ), hasOwnProperty( ) o keys( ).&lt;/p&gt;

&lt;p&gt;Prácticamente todos los objetos en Javascript son instancias de Object si seguimos la cadena de prototipos. Y como probablemente ya sabes, casi todo es un objeto en Javascript. &lt;a href="https://javascriptweblog.wordpress.com/2010/09/27/the-secret-life-of-javascript-primitives/" rel="noopener noreferrer"&gt;Incluso algunos tipos de datos primitivos (string, number y boolean específicamente) pueden ser objetos por fracciones muy pequeñas de tiempo.&lt;/a&gt; Entonces, los arrays son objetos, las funciones son objetos y, por supuesto, los objetos son objetos.&lt;/p&gt;

&lt;p&gt;La cadena prototipal nos permite crear instancias de arrays, por ejemplo, que tengan acceso a todos los métodos definidos en Array.prototype como map, forEach, filter y un gran etc. Pero también obtenemos acceso a todos los métodos disponibles en Object.prototype. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pero, ¿cómo ocurre esto?&lt;/strong&gt;&lt;br&gt;
Pues resulta que todos los objetos en Javascript tienen una propiedad oculta llamada  __proto__  que guarda una referencia al objeto prototype del constructor.  Siguiendo con el ejemplo de los arrays, un array tiene acceso a todos los métodos en Object.prototype porque cada array individual es una instancia del objeto Array, y el objeto Array es una instancia del objeto Object. &lt;strong&gt;Y esta cadena continúa hasta que llegamos al prototipo de Object.prototype, que será &lt;em&gt;null&lt;/em&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fnz67v6h1vyqxzsoxwxty.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fnz67v6h1vyqxzsoxwxty.png" alt="Alt Text" width="800" height="864"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;De esta manera, cuando intentamos ejecutar un método en un objeto, Javascript primero buscará en las propiedades del objeto en cuestión. Si no encuentra una propiedad con ese nombre buscará en su propiedad __proto__, que guarda una referencia al objeto prototype de su constructor. Si no la encuentra ahí buscará en la propiedad __proto__ del objeto constructor. Esto continuará hasta que encuentre esa propiedad o método, o no la encuentre y lance un TypeError.&lt;/p&gt;

&lt;p&gt;Por si acaso, ya que estamos usando los arrays como ejemplo, cabe recordar que los arrays son azúcar sintáctico en Javascript. Son objetos con un comportamiento especial, pero bajo la alfombra son algo así:&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    ‘0’: value,
    ‘1’: value,
    ‘2’: value
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Lo que esto significa es que, por ejemplo, cada vez que declaramos un array estamos creando una instancia del objeto Array que viene con el lenguaje. Si lo miramos en consola veremos que la propiedad __proto__ está vinculada al objeto Array:&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fu1wqdxtu34d9yo4yn841.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fu1wqdxtu34d9yo4yn841.png" alt="Alt Text" width="417" height="108"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Y si seguimos metiéndonos en la madriguera veremos que el objeto __proto__ tiene también una propiedad __proto__ que guarda una referencia a Object.prototype (es una referencia aunque nos salga en consola todo, porque ya sabes, DRY).&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fcm6voi7dqkr5c211wlge.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fcm6voi7dqkr5c211wlge.png" alt="Alt Text" width="800" height="1131"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Entonces, ¿hay alguna forma de crear un objeto sin prototipo en Javascript? &lt;br&gt;
Pues sí, la hay. Resulta que una de las formas que tenemos de crear objetos es con Object.create( ), a la cual podemos pasarle como argumento el objeto que queremos que sea el prototipo del nuevo objeto que queremos crear. Si le pasamos null estaremos creando un objeto que sea solo eso, una hash table:&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const objetoSinPrototipo = Object.create(null);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>javascript</category>
      <category>oop</category>
      <category>prototipos</category>
    </item>
    <item>
      <title>What does it mean that Javascript is prototype based?</title>
      <dc:creator>Ceci Olivera</dc:creator>
      <pubDate>Fri, 11 Sep 2020 19:10:01 +0000</pubDate>
      <link>https://dev.to/cec1_c0d/what-does-it-mean-that-javascript-is-prototype-based-30jf</link>
      <guid>https://dev.to/cec1_c0d/what-does-it-mean-that-javascript-is-prototype-based-30jf</guid>
      <description>&lt;p&gt;When I first started learning Javascript and OOP I heard over and over that Javascript is an object-oriented language though it is &lt;strong&gt;not based on classes but prototypes.&lt;/strong&gt; &lt;br&gt;
In this article we are going to try to understand what this means and why it is important to know what a prototype is to acknowledge what we are doing. &lt;/p&gt;

&lt;p&gt;In other object-oriented languages &lt;strong&gt;when you declare a class you are creating a new complex data type&lt;/strong&gt;, that is to say, a data type composed of primitive data types. But this is not what happens in Javascript even though we use the keyword class since ES2015. &lt;strong&gt;Prototypes are object instances.&lt;/strong&gt; While a class is a blueprint, &lt;strong&gt;objects inherit directly from other objects by default in Javascript.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;To understand what this&amp;nbsp;means, we need to understand what the prototype chain is.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The prototype chain is a &lt;strong&gt;tree-shaped structure that connects objects functionality and at the root of this tree is where Object.prototype lays.&lt;/strong&gt;&lt;br&gt;
Object.prototype provides a few methods that show up in all objects, such as toString( ), hasOwnProperty( ) or keys( ). &lt;/p&gt;

&lt;p&gt;Almost every object in Javascript is an instance of Object if we follow the prototype chain. And as you probably know, almost everything in Javascript is an object, &lt;a href="https://bit.ly/33m22RS" rel="noopener noreferrer"&gt;even some primitive data types (string, boolean and number specifically) can be objects for a tiny fraction of time.&lt;/a&gt; So, arrays are objects, functions are objects and, of course, objects are objects. &lt;/p&gt;

&lt;p&gt;The prototype chain allows us to create instances of, for example, arrays that have access to all the methods that are available for arrays, like map, forEach, reduce, filter, and a big etc. But arrays also have access to all the Object.prototype functionalities.&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How does this happen?&lt;/strong&gt;&lt;br&gt;
Just to be clear because we are using arrays for the example, arrays are syntactic sugar in Javascript. They are objects with a special behavior to make them look and feel like an array, but under the hood, they are something like this:&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  '0': value,
  '1': value,
  '2': value
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;It turns out that every object has a property called __proto__ which holds a reference to the prototype object of the constructor. So following the array example, an array has access to all the methods in Object.prototype because every array is an instance of the Array object and the Array object is an instance of the Object object. &lt;strong&gt;And this chain goes on until we hit the prototype of Object.prototype which will be &lt;em&gt;null&lt;/em&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This way, when we try to execute a method on an object, first JS will lookup in the properties of the object itself. If it doesn’t find a property with that name, it will look in its __proto__ property, which holds a reference to the prototype object of its constructor. If it doesn’t find it there it will look in the __proto__ property of the constructor object. This will go on until it finds it or it doesn’t find it and throws a TypeError.&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;What this means is that, for example, &lt;strong&gt;every time we declare an array we are creating an instance of the Array object that comes with the language.&lt;/strong&gt; if we look at it in the console we will see that its __proto__ property is linked to the Array object:&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F43xhyrw2uk18v8ejzzj3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F43xhyrw2uk18v8ejzzj3.png" alt="Alt Text" width="800" height="864"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;And if we keep looking down the rabbit hole we’ll see that &lt;strong&gt;the __proto__ object has a __proto__ property itself that holds a reference to Object.prototype&lt;/strong&gt; (it’s a reference even though you see all the properties in the console because you know, DRY).&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fyn7o38lw8grnzfges8cs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fyn7o38lw8grnzfges8cs.png" alt="Alt Text" width="800" height="1131"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;So, is there a way to create an object without a prototype in Javascript?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Well, yes there is. One of the ways of creating an object is with Object.create( ), to which we can pass as an argument the prototype we want that object to have, which by default is Object.prototype. If we pass it null as an argument we will get an object that is just that, a hash table.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const objectWithoutPrototype = Object.create(null);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Share if you liked it and let's connect! Follow me on &lt;a href="https://twitter.com/cec1_c0d" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt; and &lt;a href="https://www.linkedin.com/in/cecilia-olivera-webdev/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; 😊&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>prototype</category>
      <category>oop</category>
    </item>
  </channel>
</rss>
