Tickd.ai
← The Tickd Guide

Tutorials & Guides

How to Build a Local CLI to Audit Web Accessibility Violations and Auto-Fix HTML Using Gemini 1.5 Flash and Python

Writing accessible HTML is easy to ignore until a compliance audit hits. Here is how to build a local Python CLI tool that uses Gemini 1.5 Flash to automatically detect and repair web accessibility violations in your template files.

Updated 10/5/2026

Let's face it: writing semantic, accessible HTML is one of those tasks developers theoretically value but practically ignore until a compliance lawyer or a sternly worded Github issue arrives on their desk. Sifting through template folders to manually inject aria- attributes, label obscure icon buttons, and fix broken heading hierarchies is tedious work.

Automated accessibility (a11y) checkers like Axe are fantastic for flagging errors, but they leave the fixing to you. We can do better. By combining a bit of Python parsing with the speed and affordability of the Gemini API, we can build a local command-line tool that not only spots accessibility violations in your local HTML templates but actually rewrites them with clean, valid fixes.

In this guide, we will build a Python CLI that reads a directory of HTML templates, targets specific accessibility gaps, and uses /platforms/gemini to patch them in place without breaking your styling or layout.

Why Gemini 1.5 Flash?

While larger models like Claude 3.5 Sonnet excel at architectural shifts and massive refactors, web accessibility auditing is a volume game. You want a model with a massive context window (Gemini 1.5 Flash boasts 1 million tokens), low cost, and fast execution speeds. Flash is the ideal engine for processing dozens of template snippets in a single run.

Before we start coding, make sure you have your environment set up. If you encounter any API authentication issues or rate-limiting hiccups during set-up, check the Gemini support site for quick troubleshooting steps.

Project Setup and Requirements

To build this tool, we will use Python 3.10+ along with a couple of lightweight libraries. Create a new directory and install the required dependencies:

`bash mkdir a11y-autofixer && cd a11y-autofixer python3 -m venv venv source venv/bin/activate pip install google-genai beautifulsoup4 click `

Ensure you have your Gemini API key stored in your environment:

`bash export GEMINI_API_KEY="your_api_key_here" `

We will use BeautifulSoup to scan our HTML files, locate questionable snippets, and pass only the relevant, messy code chunks to Gemini. This keeps our token usage incredibly low and prevents the model from hallucinatory rewrites of your entire document layout.

Step 1: Parsing HTML for Potential Violations

Instead of dumping a massive 2,000-line HTML page into the LLM, we will parse the document locally and target common high-offence candidates for accessibility failures:

  1. Buttons and Links with no text content (e.g., <button><i class="fa-search"></i></button>).
  2. Images without alt attributes (e.g., <img src="logo.png">).
  3. Form elements without matching labels.
  4. Elements violating sequential heading structures (e.g., jumping from <h1> straight to <h4>).

Here is our initial Python class, A11yAuditor, to handle parsing and target identification. Save this as auditor.py:

`python from bs4 import BeautifulSoup import re

class A11yAuditor: def __init__(self, html_content): self.soup = BeautifulSoup(html_content, "html.parser") self.violations = []

def audit(self): self.violations = [] # 1. Look for unlabelled icon buttons or empty links for elem in self.soup.find_all(["button", "a"]): if not elem.get_text(strip=True) and not elem.get("aria-label") and not elem.get("aria-labelledby"): self.violations.append({ "type": "unlabelled_interactive", "snippet": str(elem), "line": getattr(elem, "sourceline", "Unknown") })

2. Look for missing img alt tags for img in self.soup.find_all("img"): if "alt" not in img.attrs: self.violations.append({ "type": "missing_alt", "snippet": str(img), "line": getattr(img, "sourceline", "Unknown") })

3. Look for form inputs without matching labels or aria-labels for input_tag in self.soup.find_all(["input", "select", "textarea"]): if input_tag.get("type") == "hidden": continue input_id = input_tag.get("id") has_label = False if input_id: has_label = bool(self.soup.find("label", attrs={"for": input_id})) if not has_label and not input_tag.get("aria-label") and not input_tag.get("placeholder"): self.violations.append({ "type": "unlabelled_input", "snippet": str(input_tag), "line": getattr(input_tag, "sourceline", "Unknown") })

return self.violations `

This simple parsing engine quickly isolates the worst offenders without wasting LLM capacity on pristine blocks of text.

Step 2: Designing the Gemini Repair Agent

Now, we need to design a fast pipeline to hand these isolated blocks over to Gemini 1.5 Flash. Our system prompt must instruct the model to do exactly one thing: repair the accessibility issue while retaining the exact semantic markup, CSS classes, and logic of the original tag.

We want to ensure we do not get generic conversational output back. For structured API tools, a strict JSON schema is what makes things tick.

Create a file named repair_agent.py:

`python import os import json from google import genai from google.genai import types

class RepairAgent: def __init__(self): # Initialize the official Google GenAI SDK client self.client = genai.Client()

def fix_snippet(self, violation_type, snippet): system_prompt = ( "You are an expert front-end developer specializing in web accessibility (W3C WAI-ARIA and WCAG guidelines).\n" "Your task is to repair a specific HTML accessibility violation. You must modify ONLY the element provided " "to make it fully accessible, adding attributes like 'alt', 'aria-label', or structural wrappers where appropriate.\n" "CRITICAL: Keep all original styling classes, IDs, inline styles, and framework attributes (like hx- or alpine directives) intact. " "Do not rewrite or tidy up other clean markup. Return ONLY the final corrected HTML snippet, nothing else. No markdown wrappers." )

user_prompt = f"Violation Type: {violation_type}\nSnippet to repair: {snippet}"

try: # Using Gemini 1.5 Flash for rapid, low-latency execution response = self.client.models.generate_content( model='gemini-1.5-flash', contents=user_prompt, config=types.GenerateContentConfig( system_instruction=system_prompt, temperature=0.1, # Low temperature keeps the response deterministic ) ) return response.text.strip() except Exception as e: print(f"Error processing snippet: {e}") return snippet `

For more complex schema-driven flows or structured outputs, you can consult our general guides on /prompts to fine-tune your inputs.

Step 3: Stitching the CLI Together

Now we need to assemble a clean CLI file, main.py, that will load our HTML templates, flag violations, prompt the user for validation, write the Gemini-provided fix back into the file, and save.

`python import click import os from auditor import A11yAuditor from repair_agent import RepairAgent

@click.command() @click.argument('filepath', type=click.Path(exists=True)) @click.option('--auto-apply', is_flag=True, help="Apply fixes automatically without prompting.") def main(filepath, auto_apply): """An elegant local CLI tool to audit and repair web accessibility issues in your HTML templates.""" click.echo(f"Scanning file: {filepath}...") with open(filepath, 'r', encoding='utf-8') as f: original_content = f.read()

auditor = A11yAuditor(original_content) violations = auditor.audit()

if not violations: click.echo("🎉 No accessibility violations found! Excellent job.") return

click.echo(f"Found {len(violations)} accessibility issues. Initiating repairs...") agent = RepairAgent() updated_content = original_content

for violation in violations: click.echo(f"\nLine {violation['line']}: [{violation['type']}] -> {violation['snippet']}") # Fetch the fix from Gemini fixed_snippet = agent.fix_snippet(violation['type'], violation['snippet']) click.echo(f"Proposed Fix: {fixed_snippet}") apply_fix = auto_apply if not apply_fix: apply_fix = click.confirm("Do you want to apply this fix?")

if apply_fix: # Safely replace the raw string block inside our source template updated_content = updated_content.replace(violation['snippet'], fixed_snippet) click.echo("Fix applied successfully.")

Write changes back to disk with open(filepath, 'w', encoding='utf-8') as f: f.write(updated_content) click.echo("\nDone! Your HTML file has been updated with accessible solutions.")

if __name__ == '__main__': main() `

Testing Your New CLI Tool

Let’s create a messy mock file named test_template.html to see the a11y auto-fixer in action:

`html <!DOCTYPE html> <html lang="en"> <head> <title>Messy Dashboard</title> </head> <body> <nav> <!-- Unlabelled icon link --> <a href="/dashboard" class="nav-item"><i class="icon-home"></i></a> </nav> <main> <h1>Admin Panel</h1> <!-- Image missing alt attribute --> <img src="/images/sales-chart.png" class="responsive-chart"> <div> <!-- Unlabelled text input --> <input type="text" id="username" class="form-control" placeholder="Enter username"> </div> </main> </body> </html> `

Run the CLI to audit and interactively resolve these issues:

`bash python main.py test_template.html `

You’ll watch the CLI find the exact tags, connect to Gemini, and suggest tailored modifications. For example, the tool might dynamically change the anchor tag to: `html <a href="/dashboard" class="nav-item" aria-label="Go to Dashboard"><i class="icon-home"></i></a> ` And suggest adding alt="Sales analysis chart" to your raw image tag. You have full command over what gets accepted, preventing bad or broken outputs from ever hitting your source control repository.

For more complex custom tags or components, feel free to check the /glossary to adapt your auditor selectors for framework-specific setups.

pythonaccessibilitygeminicli-toolsautomation

Keep going

Build something with the prompt generator, decode the jargon in the glossary, or compare the tools on our platform deep-dives.