MarkupGenMarkupGen
DocsResourcesBlogUse CasesCompare
Log inConvert for Free
  1. MarkupGen
  2. /Blog
  3. /Figma to Accessible HTML: A Practical Guide
Back to blog

Published 2026-08-20 · By MarkupGen Team

Figma to Accessible HTML: A Practical Guide

Figma to Accessible HTML: A Practical Guide

Quick answer: accessibility in a Figma-to-HTML workflow isn't one setting you turn on — it's a handful of specific decisions: real semantic elements instead of styled <div>s, a heading order that matches content structure (Figma has no native concept of heading levels), labeled form fields and icon-only buttons, visible focus states, sufficient color contrast, and ARIA used sparingly — only where semantic HTML can't express the interaction on its own. Automated output can get you most of the way there; a short manual pass still catches what a design file can't carry.

Why accessibility gets lost in the Figma-to-HTML translation

A Figma file describes what a design looks like. It doesn't describe what a screen reader should announce, which element should receive focus next, or whether a color pair is legible for someone with low vision. That gap is the actual reason accessibility breaks during conversion — not carelessness, but the fact that some of what accessibility needs simply isn't information a design file contains. A pixel-perfect visual match can still be an accessibility failure if the markup underneath it is generic <div>s with no structure, no labels and no keyboard support.

The fix isn't a single automated pass. It's understanding which parts of accessibility a good converter already gets right by generating real structure instead of visual-only markup, and which parts always need a deliberate decision — by a designer, a developer, or both — because the design file genuinely doesn't carry that information.

Figma design decisions that affect accessibility

A few common Figma habits create accessibility problems downstream, before any conversion tool is even involved:

  • Color as the only signal. A required form field marked red, or a disabled button that's just a lighter shade of the same color — both invisible to someone who can't distinguish those hues. The distinction needs a second signal (an icon, a label, a pattern), not just a color shift.
  • Text baked into images. A headline exported as a flattened PNG might look identical to a real text layer, but it can't be read by a screen reader, resized by the browser, or indexed by a crawler.
  • No visual focus state designed. Most Figma component sets have a default, hover and sometimes a disabled state — a focus state (for keyboard users tabbing through the page) is often just never designed, so there's nothing for the conversion to carry over.
  • Icon-only buttons with no accessible name anywhere in the file. A trash icon that means "delete" is obvious visually. Nothing in the Figma layer tells a converter — or a screen reader — what the icon means unless the layer itself is named meaningfully.
  • Inconsistent heading sizes. Figma has no "Heading 2" element the way a CMS or a word processor does — text layers are just text, styled to look a certain size. Two visually similar headings across different frames can represent completely different levels in the actual content hierarchy.

None of these are conversion bugs. They're decisions that have to be made somewhere in the pipeline, and it's worth knowing that upfront rather than assuming a tool will infer them.

Semantic HTML and landmarks

This is the part of accessibility a good Figma-to-code converter should get right by default: real <header>, <nav>, <main>, <article>, <section>, <aside> and <footer> elements instead of a page built entirely from unlabeled <div>s. Landmarks matter because they're how a screen reader user jumps directly to "main content" or "navigation" instead of tabbing through the entire page linearly. See Figma to Semantic HTML for what MarkupGen's output looks like structurally, and Is Figma-to-HTML Output SEO-Ready? for the overlap between semantic markup and crawlability — the two problems share a root cause and mostly share a fix.

Heading hierarchy

One <h1> per page, and headings that step down in order — an <h3> should be inside a section that already has an <h2>, not jumping there because a text layer happened to look that size in the design. As noted above, Figma doesn't have a native concept of heading levels, so a text layer styled large doesn't automatically become an <h1> — this is worth a manual check regardless of which tool generated the markup, because it depends on understanding the content's actual structure, not just its visual size.

Buttons vs. links

This is one of the most common accessibility mistakes in converted markup, and Figma's component structure doesn't resolve it for you: a <button> is for an action on the current page (submit, open a modal, delete an item); an <a href> is for navigation to a different page or view. A design file full of similarly-styled pill shapes gives no indication which is which — that's a semantic decision, not a visual one, and it directly affects both keyboard behavior (buttons and links respond to different keys by default) and what a screen reader announces.

Forms and labels

Every input needs a real, programmatically associated <label> — not a placeholder standing in for one. Placeholder text disappears the moment a user starts typing, has notoriously poor contrast by default, and isn't reliably read by all screen readers as a substitute for a label. If a form needs to look label-less for design reasons, use a visually-hidden label rather than removing it from the markup entirely. Error messages need to be associated with their field (commonly via aria-describedby), not just placed nearby with color alone signaling the problem.

Images and alt text

Exported images need meaningful alt attributes, not empty strings or a filename. This is one area where a manual pass is genuinely unavoidable: what a decorative background image is for, versus what a product photo needs described, isn't always obvious from the Figma frame alone — the design file doesn't carry the intent, only the pixels. Purely decorative images should get an empty alt="" (so screen readers skip them), while meaningful images need a real description of what they convey, not just what they show.

Keyboard navigation

Every interactive element — links, buttons, form fields — needs to be reachable and operable using only a keyboard, in an order that matches the visual reading order. This is where Auto Layout genuinely helps: because Auto Layout structure carries through into a logical DOM order rather than absolutely positioned fragments, tab order tends to follow reading order automatically instead of jumping around unpredictably the way it can with free-form, absolutely-positioned layouts.

Focus states

Since Figma component sets rarely include a designed focus state, this is usually the single most common accessibility gap in a converted page: a keyboard user tabs through the interface and can't visually tell where they are. At minimum, don't remove the browser's default focus outline (outline: none) without replacing it with something equally visible — a common, easy-to-ship mistake made in the name of matching a design that never accounted for it.

Color contrast

Figma lets you pick any two colors regardless of whether they're legible together, so this needs an explicit check rather than an assumption. WCAG AA asks for at least 4.5:1 contrast for normal body text and 3:1 for large text (18px+ bold, or 24px+ regular) and for meaningful UI components like icons and input borders. Run the design's actual color pairs through a contrast checker before conversion — it's far cheaper to fix a token in Figma than to chase it down across generated markup afterward.

ARIA — when it helps, and when it actively hurts

The first rule of ARIA is still the right one: no ARIA is better than bad ARIA. A native <button> already has the correct role, is focusable, and responds to Enter and Space by default — adding role="button" to it does nothing useful and risks conflicting with the element's real semantics. ARIA earns its place specifically where semantic HTML can't express the interaction on its own:

  • Use it: aria-label on an icon-only button with no visible text (a trash icon meaning "delete"), aria-expanded on a toggle controlling a collapsible section, aria-live="polite" on a region that updates without a page reload (a form validation message, a cart count).
  • Don't use it: adding ARIA roles to elements that already have correct native semantics, decorative ARIA that duplicates what's already visible and announced, or aria-live="assertive" on anything that isn't genuinely urgent — it interrupts screen reader output and is easy to overuse.

Responsive accessibility

Accessibility problems don't stay fixed across breakpoints just because they were addressed at one size. Touch targets need to stay at least roughly 44×44px on mobile even as layouts compress — a button that's comfortably clickable on desktop can become too small to tap reliably once Auto Layout resizing constraints shrink it down. Text needs to reflow rather than get clipped or overlap at narrow widths, and content order shouldn't change confusingly between breakpoints in a way that breaks the logical reading order a screen reader depends on.

Common Figma-to-HTML accessibility mistakes

  • Every clickable element converted to a <div onclick> instead of a real <button> or <a>.
  • Icon-only buttons with no aria-label, because the Figma layer was just named "Icon 4."
  • Color as the only way a state (error, disabled, selected) is communicated.
  • outline: none on focus states with nothing visible put in its place.
  • Placeholder text used as the only label on a form field.
  • Decorative images given a filename as alt text instead of an empty alt="".
  • Heading levels chosen by font size in the design instead of actual content structure.

Before / after: an icon-only button

Before — visually correct, not accessible:

<div class="icon-btn" onclick="deleteItem()">
  <svg><!-- trash icon --></svg>
</div>

After — same visual result, actually usable:

<button type="button" class="icon-btn" aria-label="Delete item" onclick="deleteItem()">
  <svg aria-hidden="true"><!-- trash icon --></svg>
</button>

The difference isn't visual at all — it's a real <button> element (focusable, keyboard-operable, announced correctly by a screen reader), an aria-label giving the icon an accessible name, and aria-hidden="true" on the decorative SVG so it isn't announced twice.

Practical accessibility checklist

  • One <h1> per page; headings step down in order, not by font size
  • Real landmark elements (header, nav, main, footer) present
  • Every clickable action is a <button>; every navigation link is an <a href>
  • Every form input has a real, associated <label> — not just a placeholder
  • Meaningful images have descriptive alt text; decorative images have alt=""
  • Focus states are visible on every interactive element
  • Tab order matches visual reading order
  • Text and UI component contrast meets WCAG AA (4.5:1 / 3:1)
  • Color is never the only signal for state or meaning
  • ARIA is used only where semantic HTML can't express the interaction
  • Touch targets stay usable at mobile breakpoints
  • Layout reflows without clipping or overlapping text at narrow widths

How generated code should be reviewed before production

MarkupGen's output favors semantic elements and a real heading structure by default rather than as an opt-in setting, and Auto Layout's structure carrying through into a logical DOM order is most of what makes correct tab order possible in the first place. The automatic AI quality score every export gets is a genuinely useful first signal — but it's explicitly a visual-match score, comparing the rendered preview against the original design. It doesn't audit ARIA correctness, contrast ratios or keyboard behavior, because none of that is visible in a screenshot comparison. Treat the checklist above as the manual pass that comes after a high visual score, not instead of it — see How to Evaluate AI-Generated HTML Before You Ship It for the same principle applied to code review more broadly.

Try it on your own design

The fastest way to see where a design needs an accessibility pass is to convert it and open the actual markup. Try MarkupGen free, or start with the full Figma-to-HTML conversion guide for the end-to-end workflow this guide builds on.

Try it with Figma to HTML

Related reading

Figma to HTML vs React vs Tailwind: Which Should You Choose?Blog

Figma to HTML vs React vs Tailwind: Which Should You Choose?

A decision guide for MarkupGen's three most-asked-about output choices — when to export Figma to HTML/CSS, when to export to React, and where Tailwind actually fits in.

Read more
Figma to Code: MarkupGen's React, Vue and CSS Framework SupportBlog

Figma to Code: MarkupGen's React, Vue and CSS Framework Support

MarkupGen converts a Figma design into React or Vue 3 components (plus Svelte and Angular), or HTML/CSS with Tailwind, Bootstrap and more.

Read more
How to Evaluate AI-Generated HTML Before You Ship ItBlog

How to Evaluate AI-Generated HTML Before You Ship It

A repeatable checklist for judging AI Figma-to-code output beyond 'it looks right' — visual fidelity, semantics, responsiveness, accessibility and weight.

Read more
Is Figma-to-HTML Output SEO-Ready? What to CheckBlog

Is Figma-to-HTML Output SEO-Ready? What to Check

Converted HTML can look identical to the design and still hurt SEO. A checklist for what to verify before publishing a Figma-to-code export.

Read more
Figma to CSS: A Practical Conversion GuideBlog

Figma to CSS: A Practical Conversion Guide

Skip the framework dependency entirely. How Figma values convert to plain, hand-editable CSS — and when that's the right call over Tailwind or Bootstrap.

Read more
Figma Design Tokens to Tailwind: Colors, Type & SpacingBlog

Figma Design Tokens to Tailwind: Colors, Type & Spacing

Why token-to-utility mapping only works as well as the Figma file's own consistency — and what actually happens when a value falls off Tailwind's scale.

Read more
Compare

MarkupGen vs. Framer: Code Export vs. Hosted Website Builder

Framer turns a Figma import into a hosted website with no native code export; MarkupGen exports standalone HTML, CSS or React that you own and host anywhere.

Read more
MarkupGen for Startups & FoundersUse Cases

MarkupGen for Startups & Founders

Turn your Figma mockup into a working site before you've hired your first front-end engineer.

Read more

Turn your next Figma design into code in minutes

Start free and export clean HTML, CSS or React from any Figma file.

Convert for Free
MarkupGenMarkupGen© 2025 MarkupGen. All rights reserved.
BlogUse CasesCompareResources
AboutDocumentationPrivacyTermsContact