[a11y] Form-input label association sweep — 13 inputs lack programmatic labels #143

Closed
opened 2026-05-07 06:34:42 +00:00 by Brouie · 7 comments
Brouie commented 2026-05-07 06:34:42 +00:00 (Migrated from gitlab.com)

@totdking — visual / a11y finding from source-level audit, assigning to you. broader pattern across the dapp where form inputs lack programmatic label association. extends DEX visual QA umbrella #133.

Scope

Source audit found 30 <input> elements in frontend-dapp/src/. ~12 lack programmatic label association (sibling label without htmlFor/id, span used as label, or placeholder-only). Two correct patterns coexist in the codebase but are inconsistently applied.

HIGH — broken label association (label sibling, no htmlFor/id)

Site Field
frontend-dapp/src/components/trade/TradeOrderTicket.tsx:281-286 Price (token1 per token0) on Trade ticket
frontend-dapp/src/components/trade/TradeOrderTicket.tsx:333-339 Cancel-limit Order ID — only placeholder="Order ID" and an h3 above
frontend-dapp/src/components/trade/LimitOrderEscrowAmountField.tsx:33-39 reusable Amount field used by both Trade & Limit Orders pages
frontend-dapp/src/pages/LimitOrdersPage.tsx:381-386 Price input
frontend-dapp/src/pages/LimitOrdersPage.tsx:436-443 Cancel Order ID
frontend-dapp/src/pages/SwapPage.tsx:701-709 Hybrid Book leg amount
frontend-dapp/src/pages/SwapPage.tsx:712-720 Hybrid Max distinct makers
frontend-dapp/src/pages/SwapPage.tsx:769-799 You Pay amount — <span class="label-neo"> (line 771) used as label; input at line 785 has neither id, aria-label, nor wrapping label
frontend-dapp/src/pages/CreatePairPage.tsx:74-81 Token A Contract Address
frontend-dapp/src/pages/CreatePairPage.tsx:97-105 Token B Contract Address
frontend-dapp/src/pages/PoolPage.tsx:710-720 LP Position size — surrounding text is a button + spans, no label/aria-label

HIGH — placeholder-only inputs (no label of any kind)

Site Field
frontend-dapp/src/pages/SwapPage.tsx:651-665 Custom slippage input — only placeholder="Custom"
frontend-dapp/src/pages/TraderPage.tsx:88-95 Wallet-search input — only placeholder="Paste a trader wallet address"

How to verify on local stack

  1. Start the local DEX stack
  2. Open the dapp at http://127.0.0.1:3000
  3. For each site listed above:
    • Navigate to the route
    • Inspect the <input> in browser devtools
    • Confirm no id matches any <label for="..."> and no aria-label/aria-labelledby is present
    • Optional: run an a11y testing tool (e.g., axe DevTools) on the page — should flag the unlabeled inputs

Reference patterns (correct sites — use as the model when fixing)

  • frontend-dapp/src/components/order/LimitOrderExpiryField.tsx:38-74 and :100-114 — htmlFor + id correctly paired
  • frontend-dapp/src/pages/ChartsPage.tsx:254-265 — pair-search has htmlFor + id + aria-label
  • frontend-dapp/src/pages/PoolPage.tsx:917-934 — pool-search has htmlFor + id

What's already PASS (don't touch)

  • All checkbox/radio inputs are correctly wrapped by an enclosing <label> (e.g., TradeOrderTicket.tsx:271-278, PoolPage.tsx:443-454, PoolPage.tsx:730-740, PoolPage.tsx:980-993, SwapPage.tsx:694-697)
  • ProtocolPage.tsx:164-171, PoolPage.tsx:456-467, PoolPage.tsx:558-569 use aria-label (acceptable but inconsistent — defer migration to umbrella decision)

Suggestion

standardise on htmlFor+id across all 13 high-priority sites. for sites already using aria-label (Protocol/Pool examples above), defer migration unless you want full consistency in one pass.

Severity

P2 — accessibility issue. screen reader users get no announced label for these inputs (only placeholder text or surrounding visual context, which screen readers don't reliably surface). matches the pattern class behind YO #179 / DEX #133 umbrella.

Cross-reference

  • DEX #133 — visual QA umbrella, unwalked. consider rolling this into checklist execution.
  • YO #179 — sibling pattern in YieldOmega frontend (single-input scope there); this DEX scope is broader (13 sites).

cc @PlasticDigits

@totdking — visual / a11y finding from source-level audit, assigning to you. broader pattern across the dapp where form inputs lack programmatic label association. extends DEX visual QA umbrella #133. ## Scope Source audit found 30 `<input>` elements in `frontend-dapp/src/`. **~12 lack programmatic label association** (sibling label without `htmlFor`/`id`, span used as label, or placeholder-only). Two correct patterns coexist in the codebase but are inconsistently applied. ## HIGH — broken label association (label sibling, no htmlFor/id) | Site | Field | |---|---| | `frontend-dapp/src/components/trade/TradeOrderTicket.tsx:281-286` | Price (token1 per token0) on Trade ticket | | `frontend-dapp/src/components/trade/TradeOrderTicket.tsx:333-339` | Cancel-limit Order ID — only `placeholder="Order ID"` and an h3 above | | `frontend-dapp/src/components/trade/LimitOrderEscrowAmountField.tsx:33-39` | reusable Amount field used by both Trade & Limit Orders pages | | `frontend-dapp/src/pages/LimitOrdersPage.tsx:381-386` | Price input | | `frontend-dapp/src/pages/LimitOrdersPage.tsx:436-443` | Cancel Order ID | | `frontend-dapp/src/pages/SwapPage.tsx:701-709` | Hybrid Book leg amount | | `frontend-dapp/src/pages/SwapPage.tsx:712-720` | Hybrid Max distinct makers | | `frontend-dapp/src/pages/SwapPage.tsx:769-799` | **You Pay** amount — `<span class="label-neo">` (line 771) used as label; input at line 785 has neither id, aria-label, nor wrapping label | | `frontend-dapp/src/pages/CreatePairPage.tsx:74-81` | Token A Contract Address | | `frontend-dapp/src/pages/CreatePairPage.tsx:97-105` | Token B Contract Address | | `frontend-dapp/src/pages/PoolPage.tsx:710-720` | LP Position size — surrounding text is a button + spans, no label/aria-label | ## HIGH — placeholder-only inputs (no label of any kind) | Site | Field | |---|---| | `frontend-dapp/src/pages/SwapPage.tsx:651-665` | Custom slippage input — only `placeholder="Custom"` | | `frontend-dapp/src/pages/TraderPage.tsx:88-95` | Wallet-search input — only `placeholder="Paste a trader wallet address"` | ## How to verify on local stack 1. Start the local DEX stack 2. Open the dapp at http://127.0.0.1:3000 3. For each site listed above: - Navigate to the route - Inspect the `<input>` in browser devtools - Confirm no `id` matches any `<label for="...">` and no `aria-label`/`aria-labelledby` is present - Optional: run an a11y testing tool (e.g., axe DevTools) on the page — should flag the unlabeled inputs ## Reference patterns (correct sites — use as the model when fixing) - `frontend-dapp/src/components/order/LimitOrderExpiryField.tsx:38-74` and `:100-114` — `htmlFor` + `id` correctly paired - `frontend-dapp/src/pages/ChartsPage.tsx:254-265` — pair-search has `htmlFor` + `id` + `aria-label` - `frontend-dapp/src/pages/PoolPage.tsx:917-934` — pool-search has `htmlFor` + `id` ## What's already PASS (don't touch) - All checkbox/radio inputs are correctly wrapped by an enclosing `<label>` (e.g., `TradeOrderTicket.tsx:271-278`, `PoolPage.tsx:443-454`, `PoolPage.tsx:730-740`, `PoolPage.tsx:980-993`, `SwapPage.tsx:694-697`) - `ProtocolPage.tsx:164-171`, `PoolPage.tsx:456-467`, `PoolPage.tsx:558-569` use `aria-label` (acceptable but inconsistent — defer migration to umbrella decision) ## Suggestion standardise on `htmlFor`+`id` across all 13 high-priority sites. for sites already using `aria-label` (Protocol/Pool examples above), defer migration unless you want full consistency in one pass. ## Severity P2 — accessibility issue. screen reader users get no announced label for these inputs (only placeholder text or surrounding visual context, which screen readers don't reliably surface). matches the pattern class behind YO #179 / DEX #133 umbrella. ## Cross-reference - DEX #133 — visual QA umbrella, unwalked. consider rolling this into checklist execution. - YO #179 — sibling pattern in YieldOmega frontend (single-input scope there); this DEX scope is broader (13 sites). cc @PlasticDigits
Brouie commented 2026-05-07 06:34:43 +00:00 (Migrated from gitlab.com)

assigned to @totdking

assigned to @totdking
Brouie commented 2026-05-07 06:37:01 +00:00 (Migrated from gitlab.com)

mentioned in issue #144

mentioned in issue #144
PlasticDigits commented 2026-05-09 05:09:04 +00:00 (Migrated from gitlab.com)

mentioned in commit 3fd47474db

mentioned in commit 3fd47474db7c5b5d2d4271518c0965813835f2f4
PlasticDigits commented 2026-05-09 05:09:26 +00:00 (Migrated from gitlab.com)

Implemented (main @ 3fd4747)

Standardized programmatic label association for the high-priority inputs from this issue using htmlFor + id, with React useId() where appropriate for stable unique IDs.

Code: `LimitOrderEscrowAmountField`, `TradeOrderTicket` (place price + cancel Order ID), `LimitOrdersPage` (price + cancel Order ID), `SwapPage` (custom slippage — sr-only label; hybrid book leg amount + max makers; You Pay amount), `CreatePairPage` (token A/B), `PoolPage` PoolCard remove-LP amount, `TraderPage` wallet search (sr-only label).

Docs: `docs/frontend.md` § Form inputs — programmatic labels. Agent playbook: `skills/AGENTS_FRONTEND_A11Y_FORM_LABELS.md` (cross-linked from `AGENTS_FRONTEND_PRODUCTION_BUILD.md`, `AGENTS_LOCALNET_TRADING_SWARM.md`).

Verification checklist

  • Local stack → open http://127.0.0.1:3000 (or your Vite port) and hit each route: Trade ticket, Limits, Swap (Settings + You Pay; optional hybrid direct swap for book leg fields), Pool (expand a pair → Remove → LP amount), Create pair, Trader search.
  • DevTools: each listed `` has `id` matching a `` (or sr-only label for trader search + custom slippage).
  • Optional: axe DevTools — no “missing label” on those controls.

@brouie Could you verify on your side when you have a moment?

Issue left open until you confirm.

## Implemented (main @ 3fd4747) Standardized **programmatic label association** for the high-priority inputs from this issue using **`htmlFor` + `id`**, with React **`useId()`** where appropriate for stable unique IDs. **Code:** \`LimitOrderEscrowAmountField\`, \`TradeOrderTicket\` (place price + cancel Order ID), \`LimitOrdersPage\` (price + cancel Order ID), \`SwapPage\` (custom slippage — sr-only label; hybrid book leg amount + max makers; You Pay amount), \`CreatePairPage\` (token A/B), \`PoolPage\` PoolCard remove-LP amount, \`TraderPage\` wallet search (sr-only label). **Docs:** \`docs/frontend.md\` § [Form inputs — programmatic labels](docs/frontend.md#form-inputs-programmatic-labels). **Agent playbook:** \`skills/AGENTS_FRONTEND_A11Y_FORM_LABELS.md\` (cross-linked from \`AGENTS_FRONTEND_PRODUCTION_BUILD.md\`, \`AGENTS_LOCALNET_TRADING_SWARM.md\`). **Verification checklist** - [ ] Local stack → open http://127.0.0.1:3000 (or your Vite port) and hit each route: **Trade** ticket, **Limits**, **Swap** (Settings + You Pay; optional hybrid direct swap for book leg fields), **Pool** (expand a pair → **Remove** → LP amount), **Create pair**, **Trader** search. - [ ] DevTools: each listed \`<input>\` has \`id\` matching a \`<label htmlFor>\` (or sr-only label for trader search + custom slippage). - [ ] Optional: axe DevTools — no “missing label” on those controls. @brouie Could you verify on your side when you have a moment? Issue left **open** until you confirm.
totdking commented 2026-05-26 15:25:05 +00:00 (Migrated from gitlab.com)

Verification checklist

  • Local stack → open http://127.0.0.1:3000 (or your Vite port) and hit each route: Trade ticket, Limits, Swap (Settings + You Pay; optional hybrid direct swap for book leg fields), Pool (expand a pair → Remove → LP amount), Create pair, Trader search.
  • DevTools: each listed `` has `id` matching a `` (or sr-only label for trader search + custom slippage).
  • Optional: axe DevTools — no “missing label” on those controls.

All 13 items pass

TradeOrderTicket.tsx

  • Price field — wired via LimitOrderPriceField component (htmlFor={inputId} + id={inputId})
  • Cancel Order ID < label htmlFor={cancelLimitOrderInputId}> + <input id={cancelLimitOrderInputId}>

LimitOrderEscrowAmountField.tsx

  • Amount field — <label htmlFor={amountInputId}>+ <input id={amountInputId}>

LimitOrdersPage.tsx

  • Price input — wired via LimitOrderPriceInputWithContext (inputId prop passed through)
  • Cancel Order ID — <label htmlFor={limitOrdersCancelOrderInputId}> + <input id={limitOrdersCancelOrderInputId}>

SwapPage.tsx

  • Custom slippage — <label htmlFor={swapCustomSlippagePctInputId} class="sr-only">
  • Hybrid Book leg amount — <label htmlFor={swapHybridBookLegAmountInputId}>
  • Hybrid Max distinct makers — <label htmlFor={swapHybridMaxMakersInputId}>
  • You Pay amount — <label htmlFor={swapYouPayAmountInputId}>

CreatePairPage.tsx

  • Token A Contract Address — <label htmlFor={tokenAInputId}> + <input id={tokenAInputId}>
  • Token B Contract Address — <label htmlFor={tokenBInputId}> + <input id={tokenBInputId}>

PoolPage.tsx

  • LP Position size — <label htmlFor={lpTokenAmountInputId}> + <input id={lpTokenAmountInputId}>

TraderPage.tsx

  • Wallet search — <label htmlFor={traderWalletSearchInputId} class="sr-only">

cc: @PlasticDigits , @Brouie

**Verification checklist** * [x] Local stack → open [http://127.0.0.1:3000](http://127.0.0.1:3000) (or your Vite port) and hit each route: **Trade** ticket, **Limits**, **Swap** (Settings + You Pay; optional hybrid direct swap for book leg fields), **Pool** (expand a pair → **Remove** → LP amount), **Create pair**, **Trader** search. * [x] DevTools: each listed \`\` has \`id\` matching a \`\` (or sr-only label for trader search + custom slippage). * [ ] Optional: axe DevTools — no “missing label” on those controls. All 13 items pass **TradeOrderTicket.tsx** * Price field — wired via LimitOrderPriceField component (htmlFor={inputId} + id={inputId}) * Cancel Order ID \< label htmlFor={cancelLimitOrderInputId}\> + \<input id={cancelLimitOrderInputId}\> **LimitOrderEscrowAmountField.tsx** * Amount field — \<label htmlFor={amountInputId}\>+ \<input id={amountInputId}\> **LimitOrdersPage.tsx** * Price input — wired via LimitOrderPriceInputWithContext (inputId prop passed through) * Cancel Order ID — \<label htmlFor={limitOrdersCancelOrderInputId}\> + \<input id={limitOrdersCancelOrderInputId}\> **SwapPage.tsx** * Custom slippage — \<label htmlFor={swapCustomSlippagePctInputId} class="sr-only"\> * Hybrid Book leg amount — \<label htmlFor={swapHybridBookLegAmountInputId}\> * Hybrid Max distinct makers — \<label htmlFor={swapHybridMaxMakersInputId}\> * You Pay amount — \<label htmlFor={swapYouPayAmountInputId}\> **CreatePairPage.tsx** * Token A Contract Address — \<label htmlFor={tokenAInputId}\> + \<input id={tokenAInputId}\> * Token B Contract Address — \<label htmlFor={tokenBInputId}\> + \<input id={tokenBInputId}\> **PoolPage.tsx** * LP Position size — \<label htmlFor={lpTokenAmountInputId}\> + \<input id={lpTokenAmountInputId}\> **TraderPage.tsx** * Wallet search — \<label htmlFor={traderWalletSearchInputId} class="sr-only"\> cc: @PlasticDigits , @Brouie
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-05-27 02:30:53 +00:00
PlasticDigits commented 2026-05-29 03:16:35 +00:00 (Migrated from gitlab.com)

mentioned in issue #214

mentioned in issue #214
PlasticDigits commented 2026-08-17 03:45:50 +00:00 (Migrated from gitlab.com)

mentioned in issue #542

mentioned in issue #542
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
code/cl8y-dex-terraclassic#143
No description provided.