Opening
I still see production sites where chemical formulas render as plain text, turning H2O into a typo instead of chemistry. When I first fixed this for a lab portal, the change was a single pair of tags, yet it removed hundreds of support tickets. In the next few minutes, you and I will treat subscript as more than a stylistic afterthought. I’ll show you how I structure in everyday HTML, how I keep it readable for screen readers, and how I fold it into modern build chains that run on MDX, design tokens, and AI-assisted linting. By the end, you’ll have patterns you can drop into chemical data, math indices, and technical docs without worrying about baseline shifts, color contrast, or hydration glitches.
Why Subscripts Matter on the Web
- Clarity: "CO2" without a lowered 2 is ambiguous in environmental dashboards and emissions calculators.
- Precision: Indices like x1 and y2 must sit below the baseline to match mathematical meaning and avoid misinterpretation in formulas or algorithm docs.
- Consistency: Design systems should treat subscript alongside bold and italics, not as an exception; otherwise, tokens and components drift.
- Accessibility: Screen readers need semantic cues; simply shrinking text is not enough.
- Globalization: Many scientific notations are language-neutral; consistent subscripts keep international content aligned without translation errors.
- Compliance: Regulated industries (pharma, energy) require accurate chemical notation in reports; improper rendering can be considered a documentation defect.
- Data trust: A single misrendered index in a KPI card can change the perceived meaning of a metric, eroding confidence in dashboards.
- Educator credibility: Courseware with sloppy indices undermines student trust and can trigger support load from confused learners.
Anatomy of and Default Rendering
The HTML spec gives us for lowered text. It lowers the baseline and reduces font size based on the UA stylesheet. I reach for it whenever the lowered position conveys meaning, not decoration.
Water: H2O
Index notation: Pi
Browsers typically render at about 75% font size and shift it down using vertical-align: sub. Those defaults are safe, but I rarely leave them untouched because typography varies across font families. On typefaces with short descenders (e.g., many geometric sans), the default vertical shift can look too shallow; on serif faces with generous descenders, it may collide with the line below. Treat the UA default as a baseline to tune.
When Is the Wrong Choice
- Pure decoration: If the lowered text is only decorative (e.g., a logo flourish), use CSS, not semantic markup.
- Exponential notation: Write 10^−6 with
; don’t fake it withsuband negative numbers. - Footnote references that must be superscript: Use
to match conventions; reservefor indices and chemical formulas. - Headings: Heavy subscript use in
can harm SEO and readability. Consider inline spans styled as subscript but keep heading text plain for search engines.
Accessible Subscripts
Screen readers read "H two O" if the markup is correct. Problems start when subscript is faked with CSS only. My checklist:
- Use real
tags so assistive tech recognizes the intent. - Add
aria-labelwhen meaning is not obvious:i. - Keep surrounding text logical; avoid splitting words into separate elements unless needed.
- Test with VoiceOver and NVDA; ensure the subscript isn’t skipped when reading continuously.
- Prefer live text over rasterized SVGs or images; AT can’t parse pixels.
- Avoid zero-width joiners or exotic Unicode subscripts; assistive tech often pronounces them incorrectly.
If a subscript stands for a footnote, I prefer linking directly:
Latency dropped to 12 ms1
SR Voice Patterns
- VoiceOver on macOS typically announces "sub" before the content if markup is correct. If you hear "underscore" instead, you’re likely using
text-decorationhacks. - NVDA on Windows speaks the glyph without saying "sub"; adding an
aria-labellike "squared" improves comprehension for math-heavy content. - For multilingual docs, set
langon the nearest container so number pronunciation matches locale.
ARIA vs Native Semantics
Resist the urge to replace with role="subscript"—there is no such ARIA role. Use the native element and enhance with aria-label only when necessary. If you must style non-semantic content to look like a subscript, wrap it in a semantic container anyway: 2two.
Keyboard Navigation
- Inline subscripts inherit the tab order of their anchor if they contain links. Keep links inside
short and purpose-driven. - Avoid
tabindexon plain; screen reader virtual cursors handle them naturally.
Styling with CSS
Default styles look fine for body copy, but scientific articles and dashboards need tighter control. I apply a small set of rules via a global utility:
sub {
font-size: 0.75em; / keeps proportions inside paragraphs /
line-height: 1; / avoids extra leading in multi-line blocks /
vertical-align: -0.25em;/ consistent shift across fonts /
}
.theme-lab sub { color: #1450a3; }
.theme-dark sub { color: #8fb1ff; }
When the brand typeface has compact descenders, I reduce the vertical shift to -0.2em. Variable fonts in 2026 often expose an optical size axis; setting font-variation-settings: "opsz" 12; inside sub keeps the smaller text crisp.
Using CSS Variables
I keep subscript tuning tokenized to avoid hard-coded magic numbers:
:root {
--sub-size: 0.76em;
--sub-shift: -0.24em;
}
sub {
font-size: var(--sub-size);
vertical-align: var(--sub-shift);
}
Theming then becomes a token swap, not a search-and-replace. In design systems, this token often lives alongside --font-body-size so typography remains proportional.
Clamp for Responsive Typography
For responsive layouts, I clamp the size so subscripts never become too small on mobile or too large in hero text:
sub {
font-size: clamp(0.68em, 0.7em + 0.2vw, 0.8em);
vertical-align: calc(-0.18em - 0.1vw);
}
Color and Contrast
Subscripts often inherit color. When brand palettes run dark-on-dark (e.g., dark theme badges), create a contrast helper:
.sub-contrast {
color: color-mix(in srgb, currentColor 80%, white 20%);
}
Use this class on subscripts in dense UI to maintain WCAG AA.
Inline vs Display Contexts
vertical-align only applies to inline or table-cell elements. If you place a inside a flex or grid container that’s set to display: block, the shift vanishes. Keep the parent inline or inline-block, or wrap the subscript in a span with display: inline.
Baseline Grid Alignment
On sites with an 8px baseline grid, subscripts can break rhythm. I nudge them with line-height: 1 and sometimes position: relative; top: 0.05em; for body text, but I avoid relative positioning in dense lists to prevent layout jitter.
Practical Patterns: Chemistry, Math, Technical Docs
Chemical formulas
Combustion Outputs
Carbon dioxide: CO2
Sulfur trioxide: SO3
Math indices
Matrix entry: Aij represents row i, column j.
Sequence term: an+1 = an + 2.
Technical references
Footnote marker: see claim4 for prior art.
Chip revision: MCUB1 shipped after week 32.
I keep the surrounding text in the same paragraph so the lowered glyphs remain contextually tied to the sentence.
Metadata and Units
Subscripts clarify units and versions:
- API revisions:
v2in docs to show version lineage without shouting. - Storage tiers:
S3IAfor infrequent access, keeping the IA visually subordinate. - Physics constants:
g0for standard gravity. - Networking:
IPv6rendered properly in diagrams and labels. - Battery cells:
18650Bdifferentiating batches in QA dashboards.
Form Fields
When collecting chemical formulas or indices in forms, keep the label rendered with while the input stays plain text. Pair with helper text showing acceptable syntax, or use a formatter that converts CO2 to CO₂ on blur to prevent data drift.
Tables and Data Grids
- Keep
inside table cells; avoid wrapping the entire cell inbecause it compresses row height. - In editable grids, render display mode with
and edit mode with raw text; swap via conditional rendering to maintain keyboard friendliness.
Edge Cases and Internationalization
- Right-to-left scripts:
respects bidi context. Test Arabic or Hebrew math snippets to confirm indices stay aligned; if they flip, wrap the formula in. - Fallback fonts: If your primary font lacks subscript numerals, browsers synthesize them by scaling. Include numeric glyphs in your subset to avoid blurry fallbacks.
- Line breaking: Avoid
white-space: nowrapon long chemical names; allow wrapping but keep tokens intact withword-break: keep-all. - Hyphenation: Hyphenated breaks inside formulas are confusing. Disable hyphenation on formula-containing paragraphs:
hyphens: manual;. - CJK typography: When mixing Latin formulas inside Japanese text, test vertical-align; some fonts offset differently. Adjust
--sub-shiftinside[lang="ja"]scopes. - Emoji adjacency: Subscripts next to emoji can misalign because emoji sit on a different baseline. Wrap the emoji in its own span or insert thin spaces.
Modern Workflows in 2026
Design systems and content pipelines make subscript handling reliable.
Table: Traditional vs Modern Handling
What it looks like
—
CO2
CO~2~ converted to
2 wrapping
Copilot rule flags CO2 and suggests CO2
[sub]2[/sub] parsed to
Node type sub rendered via component mapping
I keep a simple MDX plugin in content pipelines that translates ~ markers into so non-technical writers can still produce correct notation. In React/Next 15, a Sub component can forward props and enforce aria labels when the child is numeric, making accessibility consistent.
Build Chain Integration
- Static site generators: In Astro or Eleventy, add a remark/rehype plugin to rewrite
~digits~patterns. Keep tests so editors don’t need to memorize HTML. - Design tokens: Export
subtokens to CSS, JSON (for native apps), and PDF stylesheets so reports stay consistent across channels. - Storybook stories: Add stories showing subscripts inside headings, badges, tooltips, and tables; visual regression tests catch drift.
- Theming pipelines: When switching themes, run visual diff on sub-heavy pages to validate color and baseline integrity.
Component Patterns
React
// components/Sub.tsx
import { PropsWithChildren } from "react";
type SubProps = PropsWithChildren;
export function Sub({ children, ariaLabel, className }: SubProps) {
return (
<sub
aria-label={ariaLabel}
className={className}
style={{
fontSize: "0.78em",
verticalAlign: "-0.2em",
fontVariationSettings: ‘"opsz" 12‘
}}
>
{children}
);
}
Usage stays terse:
CO2 per mile dropped after the update.
Vue
defineProps();
.sub { font-size: 0.78em; vertical-align: -0.22em; }
Web Components
A tiny custom element can encapsulate tokens and analytics hooks:
class SubText extends HTMLElement {
connectedCallback() {
const value = this.textContent;
this.innerHTML = ${value};
this.style.fontSize = ‘0.76em‘;
this.style.verticalAlign = ‘-0.22em‘;
}
}
customElements.define(‘x-sub‘, SubText);
This makes 2 safe in static HTML while staying framework-neutral.
Design System Slotting
- Expose
Subas a typography token in Figma or your design kit so designers don’t improvise. - Map the token to code via a single source of truth JSON; regenerate platform styles weekly.
- Add analytics hooks if you need to measure formula usage in search logs.
Linting and Automation
I automate subscript correctness to keep regressions out of PRs.
- ESLint custom rule: Scan JSX/TSX for regex
CO\dand suggest; ignore strings inside code blocks. - Remark plugin for MDX: Convert
~notation and warn on naked indices. - AI lint gates: Modern CI pipelines can run a language model that checks rendered diffs for chemical or mathematical patterns. Prompt it with "flag any plain numeric index that should be subscript in chemistry or math." Use it as advisory to avoid false positives.
- Pre-commit hook: A tiny script that rewrites
([A-Z][a-z]?)(\d+)to$1$2in.htmland.mdfiles, guarded by unit tests to avoid touching years or addresses. - Content quality dashboard: Track count of untagged patterns over time; aim for zero by the end of a sprint.
Example Remark Snippet
const visit = require(‘unist-util-visit‘);
module.exports = () => (tree) => {
visit(tree, ‘text‘, (node, index, parent) => {
const match = node.value.match(/([A-Z][a-z]?)(\d+)/);
if (!match) return;
const [full, base, sub] = match;
const children = [
{ type: ‘text‘, value: base },
{ type: ‘element‘, tagName: ‘sub‘, properties: {}, children: [{ type: ‘text‘, value: sub }] }
];
parent.children.splice(index, 1, ...children);
});
};
Design Tokens and Theming
Tie subscript styling to your token system so it survives rebrands.
sub.fontSize: relative tobody.fontSize(e.g., 0.76).sub.verticalAlign: negative em value tuned per typeface.sub.color: typicallytext.secondary; override in charts to keep contrast on dark backgrounds.sub.fontVariationSettings: keep optical size crisp when variable fonts are available.sub.printSize: slightly smaller to fit dense reports.sub.emailInlineStyle: fallback values for clients that strip external CSS.
When exporting tokens to multiple platforms (CSS, iOS, Android, PDF), include subscript tokens so mobile clients render consistently.
Performance and Typography Considerations
Subscripts don’t usually affect performance, but they can trigger extra layout passes when mis-styled.
- Avoid
position: relativeon inline subscripts unless absolutely necessary;vertical-alignis faster. - Keep web font subsets; include numerals and punctuation used in indices to prevent fallback flashes.
- Test wrapped text: in narrow mobile viewports, long formulas should break at sensible points. Use
white-space: normaland allow hyphenation only when safe. - For math-heavy pages, consider MathJax or KaTeX, but keep
for lightweight indices to reduce script payloads. - Watch cumulative layout shift (CLS): late-loading web fonts can shift subscript baselines. Mitigate with
font-display: swapand matching fallback metrics. - Measure paint times with and without custom
vertical-alignoverrides; large negative values can force reflow.
Before/After Baseline Tuning
- Before: default
at 75% size withvertical-align: subyields slightly floating numbers in geometric sans. - After: custom token
font-size: 0.78em; vertical-align: -0.22em;aligns numerals flush with descenders, reducing visual jitter across paragraphs.
Print, PDF, and Export Paths
- Add print CSS:
@media print { sub { font-size: 0.7em; } }to keep indices legible on dense lab reports. - For PDF exports generated from headless Chromium, verify that the chosen font subset includes subscripts; otherwise, Chromium synthesizes them with scaling artifacts.
- In Word/RTF exports, map
to appropriate styles; many pipelines strip inline CSS, so embed styling in the HTML structure, not only in external stylesheets. - In LaTeX exports from MDX, map to
_notation automatically to maintain meaning. - For CSV exports destined for Excel, consider adding a parallel column with plain text ("CO2") so users without rich formatting still see usable data.
Emails and Legacy Clients
Email clients vary:
- Outlook desktop honors
but ignores custom CSS; rely on the native tag without heavy overrides. - Gmail strips
in the head for some accounts; inlinestyle="font-size:0.75em;vertical-align:-0.2em;"onwhen sending transactional emails with formulas. - Test with Litmus/Email on Acid or equivalent before large sends to research audiences.
- Avoid background images behind subscripts in email; contrast can fail on dark-mode auto inversion.
Dynamic Content and Editors
- WYSIWYG editors: Configure the toolbar to expose a subscript button and store content as
, not as inline styles. Quill, TipTap, and TinyMCE all allow custom formats—ensure the output HTML uses. - User input sanitization: Allow
in your HTML sanitizer (e.g., DOMPurify) so user-entered formulas survive. Pair with an allowlist rather than a rawALLOWED_TAGSoverride to avoid enabling dangerous tags. - AI-assisted authoring: Prompt models with "Use
for indices" so generated docs stay semantic. Add a post-processor that rewrites patterns likeCO2when safe. - CMS migrations: When ingesting legacy content, run an ETL step that detects patterns and rewrites to
while logging uncertain matches for human review.
Testing and Validation
I treat subscript rendering as a regression risk during font or theme changes. My routine:
- Visual tests with Playwright: assert screenshots for formulas at desktop and 360px widths.
- Axe and Lighthouse: check contrast and aria labels on subscript-heavy pages.
- Manual SR check: VoiceOver reads “C O two” instead of “C O sub two” when markup is wrong; I verify on each release.
- Print styles: add
sub { font-size: 0.7em; }in print CSS to keep lab reports legible on paper. - Unit tests for parsers: feed Markdown strings like
SO~3~and assert that the AST producesnodes. - Regression fixtures: snapshot-render components with different fonts to catch vertical-align drift after token changes.
- Performance tests: measure CLS on pages with many subscripts after font swaps.
Migration Playbook
1) Inventory: Run a repo-wide search for CO[0-9], H[0-9], v[0-9], and similar patterns. Flag hits in Markdown, JSX, and template files.
2) Autofix: Apply a conservative transform that wraps trailing digits in , skipping date patterns (years) and IP addresses.
3) Tokenize: Add --sub-size and --sub-shift to your design tokens. Document defaults and per-typeface overrides.
4) Componentize: Introduce a component or equivalent and refactor repeated markup to use it.
5) Enforce: Add lint rules and pre-commit hooks. Fail builds only after warning thresholds are exceeded to avoid blocking urgent releases.
6) Validate: Run visual diffs and screen reader spot-checks on pages with the highest chemical or mathematical density.
7) Educate: Update writer and developer docs with short examples. Add a quick tip in your CMS sidebar: "Type CO~2~ for CO₂."
8) Monitor: Track new occurrences weekly; aim for zero new violations.
Handling False Positives
During migration, auto-rewrites can hit strings like "2026". Avoid this by:
- Skipping numbers above 999 in prose blocks.
- Ignoring matches inside code fences or
tags. - Whitelisting common year patterns and version strings like "v2.1" unless explicitly marked.
Subscript in SVG and Canvas
- SVG text: Use
2insidefor charts. Pair withdominant-baseline="alphabetic"to keep alignment consistent. - Canvas: Use
ctx.font = ‘12px Inter‘; ctx.fillText(‘CO‘, x, y); ctx.fillText(‘2‘, x+measure, y+3);with manual offsets. Wrap in a helper so offsets are centralized. - Export: When exporting SVG to PNG, ensure the rendering engine respects
baseline-shift; some older libraries ignore it. Test your pipeline.
Subscript in PDFs Generated Server-Side
- With Puppeteer/Playwright to PDF, keep all subscript styling in CSS, not inline transforms. Chromium honors
vertical-alignreliably. - If generating via LaTeX, map HTML
to_automatically in the converter. Validate glyph coverage in the chosen LaTeX font. - For wkhtmltopdf, test carefully; older versions mishandle
color-mixandclamp. Use fallback declarations: define a plainfont-sizebeforeclamp.
Framework-Specific Notes
- Next.js 15 with React Server Components:
is safe in server components; hydration mismatches usually stem from differing font metrics between server and client. Keep fonts consistent and setfont-display: swap. - SvelteKit:
renders fine; when using@applywith utility classes, ensure the class isn’t purged by the build tool (add to safelist if dynamic). - Angular: Prefer a shared
SubComponentdeclared in a typography module. UseariaLabelinputs to enforce accessibility at compile time via ESLint templates. - Tailwind: Create utilities
text-subandalign-subvia plugin to centralize values. Example plugin adds.subscript { font-size: 0.76em; vertical-align: -0.22em; }.
When Not to Use
- Marketing hero text where readability outranks scientific accuracy; consider spelling out the term instead of shrinking indices.
- Logotype lockups where typographers already designed a custom mark; respect brand assets.
- Data meant for machine parsing (CSV, JSON): keep plain text and render subscripts only at presentation time.
Common Pitfalls and How I Avoid Them
- Pitfall: Using images for formulas. Fix: Keep text live; only rasterize when exporting to channels that cannot render HTML.
- Pitfall: Over-aggressive regex replacing all digits. Fix: Guard with context (capital letter + digit, small range, not a year).
- Pitfall: Flex containers swallowing
vertical-align. Fix: Keep inline context or wrap in inline elements. - Pitfall: Tiny subscripts failing contrast in dark themes. Fix: Tokenize color mix and test with WCAG tools.
- Pitfall: Screen readers skipping synthetic Unicode subscripts. Fix: Stick to
with normal digits. - Pitfall: CLS spikes when fonts load late. Fix: Use fallback metrics and
font-display: swapwith matched ascent/descent values.
Playbook for Product Teams
- Add a story in your design system called "Subscripts in Context" showcasing paragraphs, tables, badges, and tooltips.
- Include subscript scenarios in QA test plans—especially for releases that change fonts or line-height.
- Publish a short writer guide: "Use ~2~ for subscripts; avoid JPEG screenshots of formulas."
- Add a migration script to your backlog and tag it as low-risk/high-payoff; these fixes improve trust quickly.
Advanced Typography Tricks
- Use
font-feature-settings: ‘subs‘ 1;only when your font supports real subscript glyphs; otherwise browsers ignore it. When supported, you get sharper shapes than scaled-down digits. - Combine optical sizing and variable weight:
font-variation-settings: "opsz" 12, "wght" 420;insidefor crisp small text on HiDPI. - If your font ships OpenType features for scientific inferiors, enable them via
font-feature-settingsand reducevertical-alignshift because true inferiors sit lower.
Monitoring in Production
- Log client-rendered HTML for high-traffic docs and run nightly scans for
CO2without. - Add a browser-side assertion in dev builds: scan the DOM for
[data-requires-sub]attributes and warn if children lack. - Track search queries that include formulas; high search volume on misrendered terms signals missed subscripts.
Quick Reference: Do/Don’t Table
- Do use
for chemical formulas, indices, and version labels. - Do pair with
aria-labelwhen pronunciation matters. - Do tokenize size and shift.
- Don’t rely on images for text that could remain live.
- Don’t wrap entire blocks in
; keep it inline. - Don’t invent ARIA roles; rely on native semantics.
FAQ
- Does
hurt SEO? No. Search engines parse inline semantics fine. Keep headings clean if keyword weight matters. - Can I nest
? Technically yes, but it renders poorly. Rephrase or use MathML for complex nesting. - Is Unicode subscript safer? Not for accessibility; stick to
with normal digits and optionalaria-label. - What about superscript? Use
for exponents and footnotes. Keep tokens parallel so typography stays consistent. - How small is too small? Below 0.65em readability drops on mobile. Use
clampto stay above that.
Expansion Strategy (meta)
The rest of this article added deeper code examples, edge cases, production considerations, and automation patterns so you can ship subscripts confidently in modern stacks.
Closing
Subscripts look minor until a regulator, professor, or analytics user notices they’re wrong. A few lines of semantic HTML, tokenized CSS, and automated checks keep your chemistry accurate, your math precise, and your documentation trustworthy. Put these patterns into your design system, wire them into your build chain, and your team will never debate CO2 vs CO₂ again.


