Skip to content

Disable Fira Code ligatures in the Monaco editors#4430

Draft
trevor-scheer wants to merge 2 commits into
mainfrom
trevor/disable-editor-ligatures
Draft

Disable Fira Code ligatures in the Monaco editors#4430
trevor-scheer wants to merge 2 commits into
mainfrom
trevor/disable-editor-ligatures

Conversation

@trevor-scheer

Copy link
Copy Markdown
Contributor

Summary

The Monaco editors use Fira Code, whose programming ligatures rewrite certain character sequences into single glyphs. For prose that's nice; for GraphQL source it actively misrepresents the syntax.

The motivating case:

query Q($f: Int!=1) {
  allFilms(first: $f) {
    __typename
  }
}

Int!=1 means "a non-null Int (Int!) with a default value of 1 (=1)". Fira Code renders the adjacent ! and = as a ligature, so it reads as Int ≠ 1 — an inequality that has no meaning in GraphQL and directly contradicts what the code does. Other sequences (=>, ==, ->, <=, …) are similarly re-glyphed.

This PR disables ligatures so characters render literally — without regressing the Windows caret fix that is the reason ligatures were on in the first place.

Background: why ligatures were enabled

Ligatures weren't a deliberate typographic choice. They were switched on as a side effect of fixing a caret bug:

  • Issue #4040 — on Windows (Chrome/Edge), the text caret didn't reach the end of a line and Ctrl+A selections were visually misaligned.
  • PR #4046 — fixed it by setting fontLigatures: true. The commit history on that PR (try / try / see if we need font-feature-settings / back to fira code) shows it was found empirically; the mechanism was never the point, and the resulting ligatures were collateral.

Why fontLigatures: false would reintroduce the bug

The caret fix and the ligatures are the same knob, because of how Monaco decides whether it can use its "monospace" fast path for caret math.

In fontMeasurements.js:

let isMonospace = (bareFontInfo.fontFeatureSettings === EditorFontLigatures.OFF);

and EditorFontLigatures.OFF is the exact string:

static OFF = '"liga" off, "calt" off';

Monaco resolves the fontLigatures option to a font-feature-settings string:

fontLigatures value resolved font-feature-settings isMonospace
false "liga" off, "calt" off (=OFF) true
true "liga" on, "calt" on false
any other string that string verbatim false (unless it equals OFF)

When isMonospace is true, Monaco assumes every glyph is exactly typicalHalfwidthCharacterWidth and positions the caret arithmetically instead of measuring. With Fira Code on Windows, the real advance widths don't match that assumption, so the caret drifts — that's #4040.

So fontLigatures: false resolves to the exact OFF string, flips isMonospace back to true, and silently regresses the Windows caret. The two behaviors are coupled through this single string-equality check.

The fix

Pass an explicit font-feature-settings string that (a) turns ligatures off and (b) is not byte-for-byte equal to Monaco's OFF constant:

fontLigatures: '"liga" off, "calt" off, "clig" off',

The inline comment in create-editor.ts explains this coupling and points back here so the next person doesn't "simplify" it to false and quietly break Windows.

Validation Steps

  1. yarn dev (or run any GraphiQL example) and paste:
    query Q($f: Int!=1) { allFilms(first: $f) { __typename } }
    Confirm Int!=1 renders as literal characters, not Int≠1. Spot-check =>, ==, -> render literally too.
  2. Windows regression check (Chrome or Edge on Windows — the platform from [graphiql] The text cursor doesn't work well in the Playground page. It doesn't point correctly to end of the line or end of the text. #4040): type a line longer than ~20 characters, press End, and confirm the caret lands at the true end of the line; Ctrl+A should highlight the full text with no horizontal offset.

@changeset-bot

changeset-bot Bot commented Jul 15, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 131a29e

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@graphiql/react Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Fira Code's programming ligatures rewrote valid GraphQL into misleading
glyphs — `Int!=1` (a non-null `Int` defaulting to `1`) rendered as
`Int≠1`. Pass an explicit `font-feature-settings` string that turns
ligatures off but stays unequal to Monaco's OFF constant, so the
per-character caret measurement from the #4040 Windows fix is preserved.
@trevor-scheer
trevor-scheer force-pushed the trevor/disable-editor-ligatures branch from d0930c4 to 131a29e Compare July 15, 2026 02:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant