Skip to content

[web] Hidden editing <input> ignores TextCapitalization — Chromebook OSK auto-capitalizes #187231

Description

@BvG-Gynzy

Summary

On Flutter web, TextCapitalization.none (and the other values) are silently dropped between the framework and the engine. The hidden <input class="flt-text-editing"> element that the engine creates ends up without any autocapitalize attribute at all, so the platform IME falls back to its default. On Chromebook OSK this means the first letter is always auto-capitalized regardless of what the developer passes on the Dart side, diverging from iOS behavior.

Related (but separate): the Chromebook suggestion strip also appears
even with enableSuggestions: false. After investigation that turned out
to be a Gboard policy issue, not an attribute-level issue — Gboard ignores
autocomplete="off" / spellcheck="false" regardless of who sets them.
Already filed and closed as
flutter/flutter#182535.
This issue is scoped to the attribute-level bug only.

Steps to reproduce

Live demo (no setup): https://bvg-gynzy.github.io/flutter_web_input_attrs_poc/
— open this URL on a Chromebook (with the on-screen keyboard enabled) to see
the bug end-to-end, or on desktop Chrome to verify the attribute-level
mis-configuration via DevTools.

Full source: https://github.com/BvG-Gynzy/flutter_web_input_attrs_poc

  1. Create a Flutter web app with a TextField configured to opt out of
    auto-capitalization:

    TextField(textCapitalization: TextCapitalization.none)
  2. Run on Chrome: flutter run -d chrome.

  3. Focus the text field and inspect the hidden editing element
    <input class="flt-text-editing"> in DevTools (Elements panel, or
    via document.activeElement in the Console with a focusin listener
    since clicking in DevTools blurs the field).

  4. (Optional, to observe the user-facing symptom) Run on a Chromebook
    with the on-screen keyboard enabled (Settings → Accessibility →
    Keyboard → Enable on-screen keyboard) and type a single character
    without pressing Shift.

Expected results

The hidden editing element should have autocapitalize="none".

On a Chromebook OSK / Gboard, the first character typed should be
lowercase — matching iOS Safari behavior.

Actual results

On Flutter 3.41.7 the hidden editing element has no autocapitalize
attribute at all (getAttribute('autocapitalize') returns null).

On a Chromebook OSK the first character is auto-capitalized despite
TextCapitalization.none. iOS Safari with the same code behaves
correctly because of platform-specific engine handling (see Root
cause).

Desktop Chrome with a hardware keyboard shows the same missing
attribute in DevTools but does not reproduce the visible symptom,
because autocapitalize only affects virtual keyboards per the HTML
spec.

Root cause

All references below are to
engine/src/flutter/lib/web_ui/lib/src/engine/text_editing/text_editing.dart
in Flutter 3.41.7 (engine revision 7a53c052bc).

The implementation exists, but only iOS and Android strategies call it

TextCapitalizationConfig.setAutocapitalizeAttribute in
text_capitalization.dart:60 correctly maps TextCapitalization.none
"off", .sentences"sentences", etc. The mapping is fine. The
problem is who calls it:

Line Strategy Calls setAutocapitalizeAttribute?
1312 DefaultTextEditingStrategy.initializeTextEditing (base) ❌ no
1759 IOSTextEditingStrategy.initializeTextEditing ✅ yes
1915 AndroidTextEditingStrategy.initializeTextEditing ✅ yes
GloballyPositionedTextEditingStrategy (inherits default; default for Chrome OS / Chromebook) ❌ no
SafariDesktopTextEditingStrategy ❌ no
FirefoxTextEditingStrategy ❌ no

createDefaultTextEditingStrategy (line 2092) routes by platform:

if (iOS)          → IOSTextEditingStrategy        // calls it
else if (android) → AndroidTextEditingStrategy    // calls it
else if (webkit)  → SafariDesktopTextEditingStrategy   // does NOT
else if (firefox) → FirefoxTextEditingStrategy         // does NOT
elseGloballyPositionedTextEditingStrategy   // does NOT

Why Chromebook lands in the broken bucket — UA detection trace

Chrome OS User Agent (Chrome 137):

Mozilla/5.0 (X11; CrOS x86_64 14541.0.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36

Walking through browser_detection.dart:

  • detectBrowserEngineByVendorAgent (line 108):
    navigator.vendor === "Google Inc."BrowserEngine.blink.
  • detectOperatingSystem (line 150): navigator.platform on Chrome OS
    is "Linux x86_64" (the CrOS token is in the UA, not in
    navigator.platform). userAgent.contains('Android') is false.
    platform.startsWith('Linux') → returns OperatingSystem.linux.

Result: (BrowserEngine.blink, OperatingSystem.linux) → strategy
selector falls through to GloballyPositionedTextEditingStrategy. The
engine treats Chrome OS as plain Linux desktop and assumes no IME
configuration is needed.

Proposed fix

Two layers — both small and independent.

Layer 1 — apply the attribute from the base strategy. In
DefaultTextEditingStrategy.applyConfiguration, after the autocorrect
line (1373):

config.textCapitalization.setAutocapitalizeAttribute(activeDomElement);

Once the base writes autocapitalize, the iOS and Android-specific
overrides become redundant and can be deleted in the same patch.

Layer 2 — detect Chrome OS as a virtual-keyboard platform. In
detectOperatingSystem (browser_detection.dart:150), Chrome OS can
be identified by userAgent.contains('CrOS') before falling into the
Linux branch. Either:

  • Add OperatingSystem.chromeOS and route it through
    AndroidTextEditingStrategy in createDefaultTextEditingStrategy
    (cleanest, but adds enum surface), or
  • Introduce a more general hasOnScreenKeyboard predicate (true for
    iOS, Android, Chrome OS) and use it inside the strategy selector.

Layer 1 alone fixes the immediate attribute leak. Layer 2 is needed
for any other IME-related code that branches on operatingSystem and
currently makes the wrong call on Chromebook.

Code sample

A full runnable repository is available at
https://github.com/BvG-Gynzy/flutter_web_input_attrs_poc. The minimal
inline sample below is sufficient to demonstrate the bug:

Code sample
import 'package:flutter/material.dart';

void main() => runApp(const MaterialApp(home: Page()));

class Page extends StatelessWidget {
  const Page({super.key});

  @override
  Widget build(BuildContext context) {
    return const Scaffold(
      body: Padding(
        padding: EdgeInsets.all(24),
        // Expect autocapitalize="none" on the editing <input>.
        // Actual: getAttribute('autocapitalize') returns null.
        child: TextField(textCapitalization: TextCapitalization.none),
      ),
    );
  }
}

Screenshots or Video

OSK is not included when making screenshot on ChromeOS. Screenshot below shows that attributes are not correctly set on input element in Chrome on ChromeOS.

Screenshots / Video demonstration Image

Logs

Logs
[No exception is thrown; the attribute is simply not written.
No log output is produced by this bug.]

Flutter Doctor output

Doctor output
[✓] Flutter (Channel stable, 3.41.7, on macOS 26.4.1 25E253 darwin-arm64, locale en-NL)
    • Flutter version 3.41.7 on channel stable
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision cc0734ac71 (6 weeks ago), 2026-04-15 21:21:08 -0700
    • Engine revision 59aa584fdf
    • Dart version 3.11.5
    • DevTools version 2.54.2

[!] Android toolchain - develop for Android devices (Android SDK version 34.0.0)
    ✗ Flutter requires Android SDK 36 and the Android BuildTools 28.0.3
    ! Some Android licenses not accepted.

[✓] Xcode - develop for iOS and macOS (Xcode 26.5)
    • Build 17F42
    • CocoaPods version 1.16.2

[✓] Chrome - develop for the web

[✓] Connected device (2 available)
    • macOS (desktop) • macos  • darwin-arm64   • macOS 26.4.1 25E253 darwin-arm64
    • Chrome (web)    • chrome • web-javascript • Google Chrome 148.0.7778.179

[✓] Network resources
    • All expected network resources are available.

Symptoms reproduce on Chrome OS (Chromebook, Chrome 137):

Mozilla/5.0 (X11; CrOS x86_64 14541.0.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36

Metadata

Metadata

Assignees

Labels

P1High-priority issues at the top of the work lista: text inputEntering text in a text field or keyboard related problemsassigned for triageissue is assigned to a domain expert for further triagefyi-webFor the attention of Web platform teamhas reproducible stepsThe issue has been confirmed reproducible and is ready to work onplatform-chromebookChromebook applications specificallyplatform-webWeb applications specificallyteam-text-inputOwned by Text Input team

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions