DocsGuides

Guides

Accessible and localized embedded analytics

6 min readReviewed July 2026

An analytics view is not accessible because it has an aria-label, and it is not localized because its title is translated. A production implementation must preserve the meaning of the data across assistive technology, color perception, screen size, language, number format, currency, and time zone.

This guide extends Production embedded analytics. Use that guide first for tenant authorization, server-side API calls, cache isolation, and failure handling.

Choose the level of UI control#

GraphJSON offers two useful embedding boundaries:

Boundary Accessibility and localization control Use it when
GraphJSON iframe Your application controls the frame title, surrounding explanation, size, and fallback content Fast delivery and GraphJSON’s rendered chart meet the product requirement
Data API + native UI Your application controls every mark, label, focus target, table, locale, and announcement You have strict accessibility, localization, interaction, or design-system requirements

An iframe is a separate document. Your page cannot reliably add semantics to individual chart marks inside it. If a conformance requirement depends on full keyboard navigation, screen-reader exploration, translated chart internals, or a specific alternative representation, use the Data API and render the result with a component you control.

Start with an accessible information model#

Before choosing chart colors or libraries, write the information in words:

Title: Weekly active accounts
Definition: Accounts with at least one meaningful product action
Period: July 20–26, 2026
Reporting time zone: America/Los_Angeles
Primary value: 1,284 accounts
Change: Up 8.4% from the previous seven days
Notable pattern: Friday was the highest day at 231 accounts

This becomes the contract for the visible summary, chart, table alternative, and automated tests. If the meaning cannot be stated clearly, the visualization is not ready to ship.

Give every iframe a specific title#

The title attribute tells a screen-reader user what they will enter:

<iframe
  src={embedUrl}
  title="Weekly active accounts, July 20 to July 26"
  className="analytics-frame"
  loading="lazy"
/>

Avoid generic titles such as “Graph,” “Analytics,” or “Embedded content.” When several frames show the same metric for different segments, include the segment.

Add visible context outside the iframe:

<section aria-labelledby="weekly-active-accounts-title">
  <h2 id="weekly-active-accounts-title">Weekly active accounts</h2>
  <p>Accounts with at least one meaningful product action.</p>
  <p>July 20–26, 2026 · Pacific time</p>
  <iframe
    src={embedUrl}
    title="Weekly active accounts trend, July 20 to July 26"
  />
</section>

Do not use aria-hidden on a meaningful chart merely because a visible number appears nearby.

Pair visualizations with a usable alternative#

For decision-critical analytics, provide a concise text summary and a table or downloadable data view. The alternative should answer the same question, not dump every internal field.

<details>
  <summary>View weekly active accounts as a table</summary>
  <table>
    <caption>Daily active accounts, July 20–26, Pacific time</caption>
    <thead>
      <tr><th scope="col">Date</th><th scope="col">Accounts</th></tr>
    </thead>
    <tbody>
      <tr><th scope="row">July 20</th><td>184</td></tr>
      <tr><th scope="row">July 21</th><td>193</td></tr>
    </tbody>
  </table>
</details>

A sortable table must expose its current sort state and remain operable without a pointer. Keep column headers short, retain units in the caption or header, and place pagination controls in a predictable reading order.

Do not rely on color alone#

Color can reinforce a series, but it cannot be the only difference. Use combinations of:

  • direct labels
  • line style or point shape
  • position and grouping
  • icons with text
  • a legend that identifies the same visible marks

Check text, axis, gridline, focus, and data-mark contrast against your accessibility target. Test forced-colors or high-contrast modes when your application supports them. Red/green alone is especially fragile for success/failure comparisons.

For a native chart, keep a visible focus indicator and make hover-only values available by focus, tap, or a nearby table. Avoid requiring pixel-perfect pointer movement.

Design keyboard interaction intentionally#

An iframe should enter the tab order only when it contains useful interactive content. Test that a keyboard user can:

  1. reach the frame
  2. understand where focus moved
  3. operate any controls inside it
  4. leave the frame without a trap
  5. continue to the next page control

For native visualizations, prefer a small number of meaningful focus targets over making hundreds of SVG marks tabbable. A summary, controls, and data table are often more usable than mark-by-mark traversal.

Do not implement custom arrow-key behavior unless you can document and test the interaction model. Use native buttons, links, selects, and tables wherever possible.

Make state changes understandable#

Loading, empty, error, and stale are different states:

<div aria-live="polite" aria-atomic="true">
  {state === "loading" && <p>Loading weekly active accounts…</p>}
  {state === "empty" && <p>No qualifying activity in this period.</p>}
  {state === "error" && (
    <p>Weekly active accounts are temporarily unavailable.</p>
  )}
</div>

Use a polite live region for an update the user requested. Do not repeatedly announce background refreshes. Preserve the previous value when appropriate and label it as stale rather than replacing it with zero.

Skeletons should not be individually announced. Respect prefers-reduced-motion, and avoid motion that is necessary to understand the value.

Format values at the presentation boundary#

Store and transmit numbers as numbers. Apply locale, unit, and currency formatting only when rendering:

const count = new Intl.NumberFormat(locale, {
  maximumFractionDigits: 0,
}).format(1284);

const revenue = new Intl.NumberFormat(locale, {
  style: "currency",
  currency: account.currency,
}).format(15432.8);

const day = new Intl.DateTimeFormat(locale, {
  dateStyle: "medium",
  timeZone: account.reportingTimeZone,
}).format(new Date("2026-07-26T18:00:00Z"));

Do not concatenate a currency symbol or assume that commas separate thousands. Currency display, decimal separators, grouping, spacing, and negative values differ by locale.

For money stored in minor units, divide according to the currency’s exponent before formatting; do not assume every currency has two decimal places. Keep the currency code with the metric contract.

Make time zones explicit#

The same timestamp can belong to different local dates. Use one documented reporting time zone for the query and presentation, and display it near the date range.

For the Data API, send the intended IANA time zone in the GraphJSON request where supported, then format returned dates in that same zone. Do not query in UTC and silently relabel the buckets as local time.

Prefer an unambiguous label such as:

Jul 20–26, 2026 · America/Los_Angeles

You may show a localized short label such as “Pacific time,” but keep the IANA identifier available in metric details. Test daylight-saving transitions and accounts whose reporting day differs from the viewer’s device.

Localize meaning, not only strings#

Put user-facing analytics copy in the same translation system as the rest of the product:

{
  "analytics.weeklyActiveAccounts.title": "Weekly active accounts",
  "analytics.weeklyActiveAccounts.definition": "Accounts with at least one meaningful product action",
  "analytics.state.empty": "No qualifying activity in this period",
  "analytics.state.error": "Analytics are temporarily unavailable"
}

Do not assemble sentences from translated fragments. Give translators the complete message and context about variables, units, and the metric definition.

Keep stable internal report names separate from translated labels. A cache key or authorization allowlist should use weekly_active_accounts_v2, not a translated title.

Support right-to-left and long-copy layouts#

Use logical CSS properties such as margin-inline-start and padding-inline, and let the document direction control layout where possible. Do not mirror the chronological direction of a time series automatically; confirm the convention with native speakers and your charting system.

Test:

  • long German-style compound labels
  • languages without spaces
  • right-to-left page direction
  • mixed-direction IDs and numbers
  • 200% text zoom
  • narrow mobile widths

Allow titles and legends to wrap. Truncation should preserve access to the full label.

Include locale in server and cache policy#

Tenant authorization is still the first cache boundary. When presentation depends on localization, a safe cache key may include:

report-version:
account-id:
reporting-time-zone:
locale:
currency:
unit-system

If the server caches raw Data API results and formats only in the browser, locale may not affect the raw-data cache. Time zone still can because it changes grouping. Document which layer owns each transformation instead of adding or omitting dimensions by intuition.

Never accept a browser-provided account, currency, or time zone without validating it against authorized account preferences.

Build a release test matrix#

Test representative combinations rather than one ideal dashboard:

Dimension Minimum cases
Input typical, zero, empty, very large, negative where valid
State loading, ready, stale, upstream error
Access keyboard, screen reader, touch, zoom
Vision standard, reduced contrast sensitivity, forced colors
Locale default, comma-decimal, long-copy, right-to-left
Time normal week, daylight-saving transition, year boundary
Layout narrow phone, tablet, wide desktop
Tenant authorized account, denied account, recently revoked member

Automate semantics, labels, table values, authorization, and formatting. Keep manual assistive-technology testing for the interactions automation cannot meaningfully judge.

Release checklist#

  • Every iframe has a unique, specific title.
  • Each critical visualization has a text summary and usable data alternative.
  • Meaning does not depend on color or hover alone.
  • Keyboard users can enter, operate, and leave interactive content.
  • Loading, empty, stale, and error states remain distinct.
  • Live-region announcements are useful and not repetitive.
  • Number, currency, date, and time-zone formatting uses explicit locale data.
  • Long text, 200% zoom, narrow screens, and right-to-left layouts are tested.
  • Cache keys include every query or presentation dimension they actually cache.
  • Automated and manual accessibility checks are part of release review.

Use Data API response contracts when building the native rendering path, and Personalized dashboards for tenant-safe server request construction.

Need a hand?

Tell us what you’re building and we’ll point you in the right direction.

Contact support