From d4c8a331b4ab12aeb4a2df7eb1c686bc8ea61f05 Mon Sep 17 00:00:00 2001 From: Karl Koch Date: Thu, 20 Aug 2026 17:20:50 +0100 Subject: [PATCH] Stop tap flash and accidental selection on clickable controls The reset omitted labels, so tapping a checkbox still showed iOS's grey highlight. Pair -webkit-user-select everywhere selection is suppressed so a double-tap on a button doesn't select its label. Co-authored-by: Cursor --- .changeset/touch-select-papercuts.md | 14 +++++++ AGENTS.md | 8 +++- apps/docs/src/styles/global.css | 14 ++++++- .../src/components/Accordion/Accordion.css | 2 + .../components/Breadcrumbs/Breadcrumbs.css | 2 + .../elements/src/components/Button/Button.css | 3 ++ .../src/components/Carousel/Carousel.css | 2 + .../src/components/Checkbox/Checkbox.css | 2 + .../src/components/CodeBlock/CodeBlock.css | 2 +- .../src/components/Combobox/Combobox.css | 1 + .../CommandPalette/CommandPalette.css | 1 + .../src/components/DataTable/DataTable.css | 1 + .../src/components/DatePicker/DatePicker.css | 2 + .../DateRangePicker/DateRangePicker.css | 2 + .../components/DropdownMenu/DropdownMenu.css | 1 + .../src/components/FileDiff/FileDiff.css | 4 +- .../src/components/FileUpload/FileUpload.css | 2 + .../elements/src/components/Label/Label.css | 2 + packages/elements/src/components/Nav/Nav.css | 2 + .../NavigationMenu/NavigationMenu.css | 1 + .../components/NumberField/NumberField.css | 1 + .../src/components/Pagination/Pagination.css | 1 + .../src/components/RadioGroup/RadioGroup.css | 3 ++ .../src/components/Reasoning/Reasoning.css | 2 + .../src/components/Resizable/Resizable.css | 4 ++ .../elements/src/components/Select/Select.css | 1 + .../src/components/Suggestion/Suggestion.css | 1 + .../elements/src/components/Switch/Switch.css | 3 ++ .../elements/src/components/Tabs/Tabs.css | 1 + .../src/components/TagInput/TagInput.css | 3 ++ .../elements/src/components/Toast/Toast.css | 2 + .../elements/src/components/Toggle/Toggle.css | 3 ++ .../src/components/ToolCall/ToolCall.css | 2 + .../components/Accordion/Accordion.module.css | 2 + .../Breadcrumbs/Breadcrumbs.module.css | 2 + .../src/components/Button/Button.module.css | 3 ++ .../components/Carousel/Carousel.module.css | 2 + .../components/Checkbox/Checkbox.module.css | 2 + .../components/CodeBlock/CodeBlock.module.css | 2 +- .../components/Combobox/Combobox.module.css | 1 + .../CommandPalette/CommandPalette.module.css | 1 + .../components/DataTable/DataTable.module.css | 1 + .../DatePicker/DatePicker.module.css | 2 + .../DateRangePicker.module.css | 2 + .../DropdownMenu/DropdownMenu.module.css | 1 + .../components/FileDiff/FileDiff.module.css | 4 +- .../FileUpload/FileUpload.module.css | 2 + .../src/components/Label/Label.module.css | 2 + .../react/src/components/Nav/Nav.module.css | 2 + .../NavigationMenu/NavigationMenu.module.css | 1 + .../NumberField/NumberField.module.css | 1 + .../Pagination/Pagination.module.css | 1 + .../RadioGroup/RadioGroup.module.css | 3 ++ .../components/Reasoning/Reasoning.module.css | 2 + .../components/Resizable/Resizable.module.css | 4 ++ .../src/components/Select/Select.module.css | 1 + .../Suggestion/Suggestion.module.css | 1 + .../src/components/Switch/Switch.module.css | 3 ++ .../react/src/components/Tabs/Tabs.module.css | 1 + .../components/TagInput/TagInput.module.css | 3 ++ .../src/components/Toast/Toast.module.css | 2 + .../src/components/Toggle/Toggle.module.css | 3 ++ .../components/ToolCall/ToolCall.module.css | 2 + packages/styles/src/reset.css | 41 +++++++++++++++++-- scripts/check-shape-pairing.mjs | 17 ++++++-- 65 files changed, 198 insertions(+), 14 deletions(-) create mode 100644 .changeset/touch-select-papercuts.md diff --git a/.changeset/touch-select-papercuts.md b/.changeset/touch-select-papercuts.md new file mode 100644 index 0000000..4c6732d --- /dev/null +++ b/.changeset/touch-select-papercuts.md @@ -0,0 +1,14 @@ +--- +"@kernelui-lib/styles": patch +"@kernelui-lib/react": patch +"@kernelui-lib/elements": patch +--- + +Stop tap flash and accidental text selection on clickable controls. + +The reset now includes `label` (tapping a checkbox label highlighted the +label, not the input) plus the widget roles, kills the iOS long-press +callout on chrome, and sets `user-select: none` on buttons, summaries, and +labels. React and Web Components pair `-webkit-user-select` everywhere +selection is suppressed, including nav, breadcrumbs, and chips. Links stay +selectable so copying their text still works. diff --git a/AGENTS.md b/AGENTS.md index ea32f7a..9fc9dc2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -131,7 +131,13 @@ new component done, grep both `packages/react/src/index.ts` and was for, and a drag from a control selects text across the page. Pair it with `-webkit-user-select: none` for Safari. Same rule for gutters that aren't content: line numbers and diff markers are `user-select: none` so - a copy contains code, not decoration. + a copy contains code, not decoration. Tap flash and the iOS long-press + callout are a reset concern, not a per-component one: `packages/styles`' + reset kills `-webkit-tap-highlight-color` and sets `touch-action: + manipulation` on `a`/`button`/`input`/`label`/`summary` and the widget + roles, and `-webkit-touch-callout: none` on the clickable-chrome subset + (not links — long-press to open in a new tab stays). Omitting `label` + from that list is how a checkbox tap still flashes grey on iOS. - **Motion baseline** (tokens in `packages/styles/src/tokens.css`, craft bar from [transitions.dev](https://transitions.dev/) + Emil): enter with `--kernel-ease-overlay` / `--kernel-ease-out`, never `ease-in` on diff --git a/apps/docs/src/styles/global.css b/apps/docs/src/styles/global.css index 1fd82e1..388e7f4 100644 --- a/apps/docs/src/styles/global.css +++ b/apps/docs/src/styles/global.css @@ -209,6 +209,9 @@ h3 { background: none; color: var(--kernel-color-text-muted); cursor: default; + -webkit-user-select: none; + user-select: none; + -webkit-touch-callout: none; transition: background-color var(--kernel-duration-fast) var(--kernel-ease-out), color var(--kernel-duration-fast) var(--kernel-ease-out); } @@ -280,6 +283,7 @@ p.docs-sidebar-eyebrow:first-child { gap: var(--kernel-space-2); cursor: pointer; list-style: none; + -webkit-user-select: none; user-select: none; } @@ -364,6 +368,8 @@ p.docs-sidebar-eyebrow:first-child { border-inline-start: var(--kernel-border-width) solid transparent; padding-inline-start: var(--kernel-space-3); margin-inline-start: calc(var(--kernel-space-3) * -1 - var(--kernel-border-width)); + -webkit-user-select: none; + user-select: none; transition: color var(--kernel-duration-fast) var(--kernel-ease-out), border-color var(--kernel-duration-fast) var(--kernel-ease-out); } @@ -445,6 +451,8 @@ dialog.mobile-nav-panel[data-side="left"] { font-size: var(--kernel-font-size-sm); text-decoration: none; white-space: nowrap; + -webkit-user-select: none; + user-select: none; } .site-header-registry-link svg { @@ -663,6 +671,8 @@ dialog.mobile-nav-panel[data-side="left"] { minimum costs no layout. */ min-block-size: 24px; color: var(--kernel-color-text); + -webkit-user-select: none; + user-select: none; } .site-header-logo-icon { @@ -1373,8 +1383,8 @@ dialog.mobile-nav-panel[data-side="left"] { background: transparent; color: var(--kernel-color-text); cursor: grab; - user-select: none; -webkit-user-select: none; + user-select: none; inline-size: 5.5rem; flex: 0 0 auto; translate: var(--desktop-nudge-x, 0) var(--desktop-nudge-y, 0); @@ -2213,6 +2223,7 @@ dialog.mobile-nav-panel[data-side="left"] { font-weight: 500; cursor: default; list-style: none; + -webkit-user-select: none; user-select: none; transition: background-color var(--kernel-duration-fast) var(--kernel-ease-out); } @@ -2260,6 +2271,7 @@ dialog.mobile-nav-panel[data-side="left"] { resizing, matching the library Accordion's own rule. */ .usage-accordion[data-state="opening"] .usage-accordion-content, .usage-accordion[data-state="closing"] .usage-accordion-content { + -webkit-user-select: none; user-select: none; } diff --git a/packages/elements/src/components/Accordion/Accordion.css b/packages/elements/src/components/Accordion/Accordion.css index 788bd68..c487c9f 100644 --- a/packages/elements/src/components/Accordion/Accordion.css +++ b/packages/elements/src/components/Accordion/Accordion.css @@ -27,6 +27,7 @@ kernel-accordion-item:last-of-type .kernel-Accordion-item { font-weight: 500; cursor: default; list-style: none; + -webkit-user-select: none; user-select: none; transition: background-color var(--kernel-duration-fast) var(--kernel-ease-out); } @@ -85,6 +86,7 @@ kernel-accordion-item:last-of-type .kernel-Accordion-item[open] .kernel-Accordio .kernel-Accordion-item[data-state="opening"] .kernel-Accordion-content, .kernel-Accordion-item[data-state="closing"] .kernel-Accordion-content { overflow: clip; + -webkit-user-select: none; user-select: none; } diff --git a/packages/elements/src/components/Breadcrumbs/Breadcrumbs.css b/packages/elements/src/components/Breadcrumbs/Breadcrumbs.css index fdb758e..77dde50 100644 --- a/packages/elements/src/components/Breadcrumbs/Breadcrumbs.css +++ b/packages/elements/src/components/Breadcrumbs/Breadcrumbs.css @@ -46,6 +46,8 @@ kernel-breadcrumb-item { color: var(--kernel-color-text-muted); text-decoration: none; border-radius: var(--kernel-radius-sm); + -webkit-user-select: none; + user-select: none; } /* Guarded, matching the React package: on a touch device :hover latches diff --git a/packages/elements/src/components/Button/Button.css b/packages/elements/src/components/Button/Button.css index 4a5652b..3a91643 100644 --- a/packages/elements/src/components/Button/Button.css +++ b/packages/elements/src/components/Button/Button.css @@ -11,7 +11,10 @@ white-space: nowrap; text-decoration: none; cursor: default; + -webkit-user-select: none; user-select: none; + -webkit-touch-callout: none; + -webkit-tap-highlight-color: transparent; transition: background-color var(--kernel-duration-fast) var(--kernel-ease-out), border-color var(--kernel-duration-fast) var(--kernel-ease-out), diff --git a/packages/elements/src/components/Carousel/Carousel.css b/packages/elements/src/components/Carousel/Carousel.css index b799357..8b312bf 100644 --- a/packages/elements/src/components/Carousel/Carousel.css +++ b/packages/elements/src/components/Carousel/Carousel.css @@ -49,6 +49,7 @@ kernel-carousel-slide { cursor: default; /* Same convention as Button/Checkbox/Switch: a clickable control's own label text never drag-selects. */ + -webkit-user-select: none; user-select: none; transition: background-color var(--kernel-duration-fast) var(--kernel-ease-out); } @@ -96,6 +97,7 @@ kernel-carousel-slide { border-radius: var(--kernel-radius-full); background-color: var(--kernel-color-border-interactive); cursor: default; + -webkit-user-select: none; user-select: none; transition: background-color var(--kernel-duration-fast) var(--kernel-ease-out); } diff --git a/packages/elements/src/components/Checkbox/Checkbox.css b/packages/elements/src/components/Checkbox/Checkbox.css index 1339d8b..cf84f58 100644 --- a/packages/elements/src/components/Checkbox/Checkbox.css +++ b/packages/elements/src/components/Checkbox/Checkbox.css @@ -6,6 +6,8 @@ cursor: pointer; -webkit-user-select: none; user-select: none; + -webkit-touch-callout: none; + -webkit-tap-highlight-color: transparent; font-family: var(--kernel-font-sans); font-size: var(--kernel-font-size-base); color: var(--kernel-color-text); diff --git a/packages/elements/src/components/CodeBlock/CodeBlock.css b/packages/elements/src/components/CodeBlock/CodeBlock.css index 7def51b..e199af9 100644 --- a/packages/elements/src/components/CodeBlock/CodeBlock.css +++ b/packages/elements/src/components/CodeBlock/CodeBlock.css @@ -191,8 +191,8 @@ kernel-code-block { color: var(--kernel-color-text-muted); font-variant-numeric: tabular-nums; text-align: end; - user-select: none; -webkit-user-select: none; + user-select: none; } .kernel-CodeBlock-lineText { diff --git a/packages/elements/src/components/Combobox/Combobox.css b/packages/elements/src/components/Combobox/Combobox.css index 9ccb78a..ff478f0 100644 --- a/packages/elements/src/components/Combobox/Combobox.css +++ b/packages/elements/src/components/Combobox/Combobox.css @@ -93,6 +93,7 @@ cursor: default; /* Same convention as Button/Checkbox/Switch: a clickable control's own label text never drag-selects. */ + -webkit-user-select: none; user-select: none; } diff --git a/packages/elements/src/components/CommandPalette/CommandPalette.css b/packages/elements/src/components/CommandPalette/CommandPalette.css index f8f32d1..e79bf04 100644 --- a/packages/elements/src/components/CommandPalette/CommandPalette.css +++ b/packages/elements/src/components/CommandPalette/CommandPalette.css @@ -155,6 +155,7 @@ cursor: default; /* Same convention as Button/Checkbox/Switch: a clickable control's own label text never drag-selects. */ + -webkit-user-select: none; user-select: none; } diff --git a/packages/elements/src/components/DataTable/DataTable.css b/packages/elements/src/components/DataTable/DataTable.css index 82df5a9..341e2f4 100644 --- a/packages/elements/src/components/DataTable/DataTable.css +++ b/packages/elements/src/components/DataTable/DataTable.css @@ -28,6 +28,7 @@ cursor: pointer; /* Same convention as Button/Checkbox/Switch: a clickable control's own label text never drag-selects. */ + -webkit-user-select: none; user-select: none; } diff --git a/packages/elements/src/components/DatePicker/DatePicker.css b/packages/elements/src/components/DatePicker/DatePicker.css index 58100bb..6073800 100644 --- a/packages/elements/src/components/DatePicker/DatePicker.css +++ b/packages/elements/src/components/DatePicker/DatePicker.css @@ -36,6 +36,7 @@ cursor: pointer; /* Same convention as Button/Checkbox/Switch: a clickable control's own label text never drag-selects. */ + -webkit-user-select: none; user-select: none; transition: background-color var(--kernel-duration-fast) var(--kernel-ease-out), @@ -96,6 +97,7 @@ cursor: pointer; /* Same convention as Button/Checkbox/Switch: a clickable control's own label text never drag-selects. */ + -webkit-user-select: none; user-select: none; transition: background-color var(--kernel-duration-fast) var(--kernel-ease-out), diff --git a/packages/elements/src/components/DateRangePicker/DateRangePicker.css b/packages/elements/src/components/DateRangePicker/DateRangePicker.css index 2421da8..e2cad84 100644 --- a/packages/elements/src/components/DateRangePicker/DateRangePicker.css +++ b/packages/elements/src/components/DateRangePicker/DateRangePicker.css @@ -36,6 +36,7 @@ cursor: pointer; /* Same convention as Button/Checkbox/Switch: a clickable control's own label text never drag-selects. */ + -webkit-user-select: none; user-select: none; transition: background-color var(--kernel-duration-fast) var(--kernel-ease-out), @@ -100,6 +101,7 @@ cursor: pointer; /* Also matters here beyond the usual convention: hovering across cells while previewing/selecting a range must not drag-select day text. */ + -webkit-user-select: none; user-select: none; transition: scale var(--kernel-duration-fast) var(--kernel-ease-out); } diff --git a/packages/elements/src/components/DropdownMenu/DropdownMenu.css b/packages/elements/src/components/DropdownMenu/DropdownMenu.css index 694320a..d3c8e50 100644 --- a/packages/elements/src/components/DropdownMenu/DropdownMenu.css +++ b/packages/elements/src/components/DropdownMenu/DropdownMenu.css @@ -81,6 +81,7 @@ kernel-menu-separator { cursor: default; /* Same convention as Button/Checkbox/Switch: a clickable control's own label text never drag-selects. */ + -webkit-user-select: none; user-select: none; transition: background-color var(--kernel-duration-fast) var(--kernel-ease-out), diff --git a/packages/elements/src/components/FileDiff/FileDiff.css b/packages/elements/src/components/FileDiff/FileDiff.css index df95a4d..b872a9a 100644 --- a/packages/elements/src/components/FileDiff/FileDiff.css +++ b/packages/elements/src/components/FileDiff/FileDiff.css @@ -126,8 +126,8 @@ kernel-file-diff { text-align: end; vertical-align: top; white-space: nowrap; - user-select: none; -webkit-user-select: none; + user-select: none; } /* The +/− marker is real text, not a colour. Colour alone doesn't say whether @@ -137,8 +137,8 @@ kernel-file-diff { padding-inline-start: var(--kernel-space-2); vertical-align: top; white-space: pre; - user-select: none; -webkit-user-select: none; + user-select: none; } .kernel-FileDiff-row[data-kind="add"] .kernel-FileDiff-marker { diff --git a/packages/elements/src/components/FileUpload/FileUpload.css b/packages/elements/src/components/FileUpload/FileUpload.css index 52903b6..e44a9c2 100644 --- a/packages/elements/src/components/FileUpload/FileUpload.css +++ b/packages/elements/src/components/FileUpload/FileUpload.css @@ -44,6 +44,7 @@ text-align: center; cursor: pointer; overflow: hidden; + -webkit-user-select: none; user-select: none; transition: border-color var(--kernel-duration-enter) var(--kernel-ease-overlay), @@ -236,6 +237,7 @@ /* A drag gesture that starts on/near a file row shouldn't fight the browser's own text-selection heuristics — nothing in the row is meant to be selected as text. */ + -webkit-user-select: none; user-select: none; } diff --git a/packages/elements/src/components/Label/Label.css b/packages/elements/src/components/Label/Label.css index bc9143d..c7d4f52 100644 --- a/packages/elements/src/components/Label/Label.css +++ b/packages/elements/src/components/Label/Label.css @@ -4,6 +4,8 @@ font-size: var(--kernel-font-size-sm); font-weight: 500; color: var(--kernel-color-text); + -webkit-user-select: none; + user-select: none; } .kernel-Label-required { diff --git a/packages/elements/src/components/Nav/Nav.css b/packages/elements/src/components/Nav/Nav.css index 5312d80..b329f1c 100644 --- a/packages/elements/src/components/Nav/Nav.css +++ b/packages/elements/src/components/Nav/Nav.css @@ -36,6 +36,8 @@ kernel-nav-link { color: var(--kernel-color-text-muted); text-decoration: none; font-size: var(--kernel-font-size-sm); + -webkit-user-select: none; + user-select: none; transition: background-color var(--kernel-duration-fast) var(--kernel-ease-out), color var(--kernel-duration-fast) var(--kernel-ease-out); diff --git a/packages/elements/src/components/NavigationMenu/NavigationMenu.css b/packages/elements/src/components/NavigationMenu/NavigationMenu.css index cc05bd1..9d114b3 100644 --- a/packages/elements/src/components/NavigationMenu/NavigationMenu.css +++ b/packages/elements/src/components/NavigationMenu/NavigationMenu.css @@ -37,6 +37,7 @@ kernel-nav-menu-item { cursor: default; /* Same convention as Button/Checkbox/Switch: a clickable control's own label text never drag-selects. */ + -webkit-user-select: none; user-select: none; transition: background-color var(--kernel-duration-fast) var(--kernel-ease-out), diff --git a/packages/elements/src/components/NumberField/NumberField.css b/packages/elements/src/components/NumberField/NumberField.css index dfb0be3..547fee1 100644 --- a/packages/elements/src/components/NumberField/NumberField.css +++ b/packages/elements/src/components/NumberField/NumberField.css @@ -142,6 +142,7 @@ cursor: pointer; /* Same convention as Button/Checkbox/Switch: a clickable control's own label text never drag-selects. */ + -webkit-user-select: none; user-select: none; transition: background-color var(--kernel-duration-fast) var(--kernel-ease-out), diff --git a/packages/elements/src/components/Pagination/Pagination.css b/packages/elements/src/components/Pagination/Pagination.css index b2ee560..bd93b6f 100644 --- a/packages/elements/src/components/Pagination/Pagination.css +++ b/packages/elements/src/components/Pagination/Pagination.css @@ -38,6 +38,7 @@ kernel-pagination-ellipsis { font-variant-numeric: tabular-nums; /* Same convention as Button/Checkbox/Switch: a clickable control's own label text never drag-selects. */ + -webkit-user-select: none; user-select: none; transition: background-color var(--kernel-duration-fast) var(--kernel-ease-out); } diff --git a/packages/elements/src/components/RadioGroup/RadioGroup.css b/packages/elements/src/components/RadioGroup/RadioGroup.css index 01d38da..35aaa96 100644 --- a/packages/elements/src/components/RadioGroup/RadioGroup.css +++ b/packages/elements/src/components/RadioGroup/RadioGroup.css @@ -32,6 +32,8 @@ cursor: pointer; -webkit-user-select: none; user-select: none; + -webkit-touch-callout: none; + -webkit-tap-highlight-color: transparent; font-family: var(--kernel-font-sans); color: var(--kernel-color-text); } @@ -108,6 +110,7 @@ } .kernel-RadioGroup-label { + -webkit-user-select: none; user-select: none; } diff --git a/packages/elements/src/components/Reasoning/Reasoning.css b/packages/elements/src/components/Reasoning/Reasoning.css index 5ec6120..a3a10ce 100644 --- a/packages/elements/src/components/Reasoning/Reasoning.css +++ b/packages/elements/src/components/Reasoning/Reasoning.css @@ -10,6 +10,7 @@ padding-block: var(--kernel-space-2); cursor: default; list-style: none; + -webkit-user-select: none; user-select: none; } @@ -65,6 +66,7 @@ .kernel-Reasoning-root[data-state="opening"] .kernel-Reasoning-content, .kernel-Reasoning-root[data-state="closing"] .kernel-Reasoning-content { overflow: clip; + -webkit-user-select: none; user-select: none; } diff --git a/packages/elements/src/components/Resizable/Resizable.css b/packages/elements/src/components/Resizable/Resizable.css index 86f3203..1fb62a3 100644 --- a/packages/elements/src/components/Resizable/Resizable.css +++ b/packages/elements/src/components/Resizable/Resizable.css @@ -15,6 +15,7 @@ } .kernel-Resizable-root[data-dragging] { + -webkit-user-select: none; user-select: none; } @@ -28,6 +29,9 @@ position: relative; flex-shrink: 0; background-color: var(--kernel-color-border); + -webkit-user-select: none; + user-select: none; + -webkit-touch-callout: none; transition: background-color var(--kernel-duration-fast) var(--kernel-ease-out); } diff --git a/packages/elements/src/components/Select/Select.css b/packages/elements/src/components/Select/Select.css index 20f0588..ae9963d 100644 --- a/packages/elements/src/components/Select/Select.css +++ b/packages/elements/src/components/Select/Select.css @@ -40,6 +40,7 @@ font-size: var(--kernel-font-size-base); /* Same convention as Button/Checkbox/Switch: a clickable control's own label text never drag-selects. */ + -webkit-user-select: none; user-select: none; transition: border-color var(--kernel-duration-fast) var(--kernel-ease-out), scale var(--kernel-duration-fast) var(--kernel-ease-out); diff --git a/packages/elements/src/components/Suggestion/Suggestion.css b/packages/elements/src/components/Suggestion/Suggestion.css index 7cb2442..785b455 100644 --- a/packages/elements/src/components/Suggestion/Suggestion.css +++ b/packages/elements/src/components/Suggestion/Suggestion.css @@ -41,6 +41,7 @@ cursor: default; /* Same convention as Button/Checkbox/Switch: a clickable control's own label text never drag-selects. */ + -webkit-user-select: none; user-select: none; transition: background-color var(--kernel-duration-fast) var(--kernel-ease-out), diff --git a/packages/elements/src/components/Switch/Switch.css b/packages/elements/src/components/Switch/Switch.css index a332c04..746e0b0 100644 --- a/packages/elements/src/components/Switch/Switch.css +++ b/packages/elements/src/components/Switch/Switch.css @@ -6,6 +6,8 @@ cursor: pointer; -webkit-user-select: none; user-select: none; + -webkit-touch-callout: none; + -webkit-tap-highlight-color: transparent; font-family: var(--kernel-font-sans); color: var(--kernel-color-text); } @@ -78,6 +80,7 @@ } .kernel-Switch-label { + -webkit-user-select: none; user-select: none; } diff --git a/packages/elements/src/components/Tabs/Tabs.css b/packages/elements/src/components/Tabs/Tabs.css index 8fa1bea..9bb5bc1 100644 --- a/packages/elements/src/components/Tabs/Tabs.css +++ b/packages/elements/src/components/Tabs/Tabs.css @@ -46,6 +46,7 @@ kernel-tabs { cursor: default; /* Same convention as Button/Checkbox/Switch: a clickable control's own label text never drag-selects. */ + -webkit-user-select: none; user-select: none; transition: background-color var(--kernel-duration-fast) var(--kernel-ease-out), diff --git a/packages/elements/src/components/TagInput/TagInput.css b/packages/elements/src/components/TagInput/TagInput.css index 5b52c6c..ea940f3 100644 --- a/packages/elements/src/components/TagInput/TagInput.css +++ b/packages/elements/src/components/TagInput/TagInput.css @@ -100,6 +100,8 @@ opacity: 1; scale: 1; filter: blur(0); + -webkit-user-select: none; + user-select: none; transition: opacity var(--kernel-duration-exit-fast) linear, scale var(--kernel-duration-exit-fast) var(--kernel-ease-overlay), @@ -133,6 +135,7 @@ cursor: pointer; /* Same convention as Button/Checkbox/Switch: a clickable control's own label text never drag-selects. */ + -webkit-user-select: none; user-select: none; transition: opacity var(--kernel-duration-fast) var(--kernel-ease-out), diff --git a/packages/elements/src/components/Toast/Toast.css b/packages/elements/src/components/Toast/Toast.css index 5bfb30b..c4f142d 100644 --- a/packages/elements/src/components/Toast/Toast.css +++ b/packages/elements/src/components/Toast/Toast.css @@ -29,6 +29,7 @@ /* The whole toast is swipe-to-dismiss draggable; without this a drag gesture starting on its text selects that text instead of moving the toast. */ + -webkit-user-select: none; user-select: none; --y: 0px; @@ -196,6 +197,7 @@ cursor: default; /* Same convention as Button/Checkbox/Switch: a clickable control's own label text never drag-selects. */ + -webkit-user-select: none; user-select: none; opacity: 0.7; transition: diff --git a/packages/elements/src/components/Toggle/Toggle.css b/packages/elements/src/components/Toggle/Toggle.css index 6f22f5b..f4405f9 100644 --- a/packages/elements/src/components/Toggle/Toggle.css +++ b/packages/elements/src/components/Toggle/Toggle.css @@ -10,7 +10,10 @@ cursor: default; /* Same convention as Button/Checkbox/Switch: a clickable control's own label text never drag-selects. */ + -webkit-user-select: none; user-select: none; + -webkit-touch-callout: none; + -webkit-tap-highlight-color: transparent; transition: background-color var(--kernel-duration-fast) var(--kernel-ease-out), color var(--kernel-duration-fast) var(--kernel-ease-out), diff --git a/packages/elements/src/components/ToolCall/ToolCall.css b/packages/elements/src/components/ToolCall/ToolCall.css index 56c0c07..66a752d 100644 --- a/packages/elements/src/components/ToolCall/ToolCall.css +++ b/packages/elements/src/components/ToolCall/ToolCall.css @@ -17,6 +17,7 @@ padding-block: var(--kernel-space-2); cursor: default; list-style: none; + -webkit-user-select: none; user-select: none; } @@ -147,6 +148,7 @@ .kernel-ToolCall-root[data-state="opening"] .kernel-ToolCall-content, .kernel-ToolCall-root[data-state="closing"] .kernel-ToolCall-content { overflow: clip; + -webkit-user-select: none; user-select: none; } diff --git a/packages/react/src/components/Accordion/Accordion.module.css b/packages/react/src/components/Accordion/Accordion.module.css index 256984b..3031508 100644 --- a/packages/react/src/components/Accordion/Accordion.module.css +++ b/packages/react/src/components/Accordion/Accordion.module.css @@ -25,6 +25,7 @@ list-style: none; /* Same convention as Button/Checkbox/Switch: a clickable control's own label text never drag-selects. */ + -webkit-user-select: none; user-select: none; transition: background-color var(--kernel-duration-fast) var(--kernel-ease-out); } @@ -100,6 +101,7 @@ .item[data-state="opening"] .content, .item[data-state="closing"] .content { overflow: clip; + -webkit-user-select: none; user-select: none; } diff --git a/packages/react/src/components/Breadcrumbs/Breadcrumbs.module.css b/packages/react/src/components/Breadcrumbs/Breadcrumbs.module.css index 493dd61..00dcede 100644 --- a/packages/react/src/components/Breadcrumbs/Breadcrumbs.module.css +++ b/packages/react/src/components/Breadcrumbs/Breadcrumbs.module.css @@ -42,6 +42,8 @@ color: var(--kernel-color-text-muted); text-decoration: none; border-radius: var(--kernel-radius-sm); + -webkit-user-select: none; + user-select: none; } @media (hover: hover) and (pointer: fine) { diff --git a/packages/react/src/components/Button/Button.module.css b/packages/react/src/components/Button/Button.module.css index 0eae72b..cf538d1 100644 --- a/packages/react/src/components/Button/Button.module.css +++ b/packages/react/src/components/Button/Button.module.css @@ -11,7 +11,10 @@ white-space: nowrap; text-decoration: none; cursor: default; + -webkit-user-select: none; user-select: none; + -webkit-touch-callout: none; + -webkit-tap-highlight-color: transparent; transition: background-color var(--kernel-duration-fast) var(--kernel-ease-out), border-color var(--kernel-duration-fast) var(--kernel-ease-out), diff --git a/packages/react/src/components/Carousel/Carousel.module.css b/packages/react/src/components/Carousel/Carousel.module.css index 9b56e4d..5e3c931 100644 --- a/packages/react/src/components/Carousel/Carousel.module.css +++ b/packages/react/src/components/Carousel/Carousel.module.css @@ -56,6 +56,7 @@ cursor: default; /* Same convention as Button/Checkbox/Switch: a clickable control's own label text never drag-selects. */ + -webkit-user-select: none; user-select: none; transition: background-color var(--kernel-duration-fast) var(--kernel-ease-out); } @@ -106,6 +107,7 @@ border-radius: var(--kernel-radius-full); background-color: var(--kernel-color-border-interactive); cursor: default; + -webkit-user-select: none; user-select: none; transition: background-color var(--kernel-duration-fast) var(--kernel-ease-out); } diff --git a/packages/react/src/components/Checkbox/Checkbox.module.css b/packages/react/src/components/Checkbox/Checkbox.module.css index b5d5b77..2d7dc1a 100644 --- a/packages/react/src/components/Checkbox/Checkbox.module.css +++ b/packages/react/src/components/Checkbox/Checkbox.module.css @@ -6,6 +6,8 @@ cursor: pointer; -webkit-user-select: none; user-select: none; + -webkit-touch-callout: none; + -webkit-tap-highlight-color: transparent; font-family: var(--kernel-font-sans); font-size: var(--kernel-font-size-base); color: var(--kernel-color-text); diff --git a/packages/react/src/components/CodeBlock/CodeBlock.module.css b/packages/react/src/components/CodeBlock/CodeBlock.module.css index 40b90e6..f000bf4 100644 --- a/packages/react/src/components/CodeBlock/CodeBlock.module.css +++ b/packages/react/src/components/CodeBlock/CodeBlock.module.css @@ -186,8 +186,8 @@ color: var(--kernel-color-text-muted); font-variant-numeric: tabular-nums; text-align: end; - user-select: none; -webkit-user-select: none; + user-select: none; } .lineText { diff --git a/packages/react/src/components/Combobox/Combobox.module.css b/packages/react/src/components/Combobox/Combobox.module.css index 09111e8..0cd2d43 100644 --- a/packages/react/src/components/Combobox/Combobox.module.css +++ b/packages/react/src/components/Combobox/Combobox.module.css @@ -121,6 +121,7 @@ cursor: default; /* Same convention as Button/Checkbox/Switch: a clickable control's own label text never drag-selects. */ + -webkit-user-select: none; user-select: none; } diff --git a/packages/react/src/components/CommandPalette/CommandPalette.module.css b/packages/react/src/components/CommandPalette/CommandPalette.module.css index e673ee4..d487379 100644 --- a/packages/react/src/components/CommandPalette/CommandPalette.module.css +++ b/packages/react/src/components/CommandPalette/CommandPalette.module.css @@ -155,6 +155,7 @@ cursor: default; /* Same convention as Button/Checkbox/Switch: a clickable control's own label text never drag-selects. */ + -webkit-user-select: none; user-select: none; } diff --git a/packages/react/src/components/DataTable/DataTable.module.css b/packages/react/src/components/DataTable/DataTable.module.css index 19cae83..b1fcfdf 100644 --- a/packages/react/src/components/DataTable/DataTable.module.css +++ b/packages/react/src/components/DataTable/DataTable.module.css @@ -28,6 +28,7 @@ cursor: pointer; /* Same convention as Button/Checkbox/Switch: a clickable control's own label text never drag-selects. */ + -webkit-user-select: none; user-select: none; } diff --git a/packages/react/src/components/DatePicker/DatePicker.module.css b/packages/react/src/components/DatePicker/DatePicker.module.css index 5488882..7aaf38e 100644 --- a/packages/react/src/components/DatePicker/DatePicker.module.css +++ b/packages/react/src/components/DatePicker/DatePicker.module.css @@ -36,6 +36,7 @@ cursor: pointer; /* Same convention as Button/Checkbox/Switch: a clickable control's own label text never drag-selects. */ + -webkit-user-select: none; user-select: none; transition: background-color var(--kernel-duration-fast) var(--kernel-ease-out), border-color var(--kernel-duration-fast) var(--kernel-ease-out), scale var(--kernel-duration-fast) var(--kernel-ease-out); @@ -94,6 +95,7 @@ cursor: pointer; /* Same convention as Button/Checkbox/Switch: a clickable control's own label text never drag-selects. */ + -webkit-user-select: none; user-select: none; transition: background-color var(--kernel-duration-fast) var(--kernel-ease-out), color var(--kernel-duration-fast) var(--kernel-ease-out), scale var(--kernel-duration-fast) var(--kernel-ease-out); diff --git a/packages/react/src/components/DateRangePicker/DateRangePicker.module.css b/packages/react/src/components/DateRangePicker/DateRangePicker.module.css index 9d79251..5b2b48a 100644 --- a/packages/react/src/components/DateRangePicker/DateRangePicker.module.css +++ b/packages/react/src/components/DateRangePicker/DateRangePicker.module.css @@ -36,6 +36,7 @@ cursor: pointer; /* Same convention as Button/Checkbox/Switch: a clickable control's own label text never drag-selects. */ + -webkit-user-select: none; user-select: none; transition: background-color var(--kernel-duration-fast) var(--kernel-ease-out), border-color var(--kernel-duration-fast) var(--kernel-ease-out), scale var(--kernel-duration-fast) var(--kernel-ease-out); @@ -103,6 +104,7 @@ cursor: pointer; /* Also matters here beyond the usual convention: hovering across cells while previewing/selecting a range must not drag-select day text. */ + -webkit-user-select: none; user-select: none; transition: scale var(--kernel-duration-fast) var(--kernel-ease-out); } diff --git a/packages/react/src/components/DropdownMenu/DropdownMenu.module.css b/packages/react/src/components/DropdownMenu/DropdownMenu.module.css index 439b16c..0143a16 100644 --- a/packages/react/src/components/DropdownMenu/DropdownMenu.module.css +++ b/packages/react/src/components/DropdownMenu/DropdownMenu.module.css @@ -77,6 +77,7 @@ cursor: default; /* Same convention as Button/Checkbox/Switch: a clickable control's own label text never drag-selects. */ + -webkit-user-select: none; user-select: none; transition: background-color var(--kernel-duration-fast) var(--kernel-ease-out), diff --git a/packages/react/src/components/FileDiff/FileDiff.module.css b/packages/react/src/components/FileDiff/FileDiff.module.css index b86ae59..e9e7f9f 100644 --- a/packages/react/src/components/FileDiff/FileDiff.module.css +++ b/packages/react/src/components/FileDiff/FileDiff.module.css @@ -119,8 +119,8 @@ text-align: end; vertical-align: top; white-space: nowrap; - user-select: none; -webkit-user-select: none; + user-select: none; } /* The +/− marker is real text, not a colour. Colour alone doesn't say whether @@ -130,8 +130,8 @@ padding-inline-start: var(--kernel-space-2); vertical-align: top; white-space: pre; - user-select: none; -webkit-user-select: none; + user-select: none; } .row[data-kind="add"] .marker { diff --git a/packages/react/src/components/FileUpload/FileUpload.module.css b/packages/react/src/components/FileUpload/FileUpload.module.css index 0728af8..b87bcfd 100644 --- a/packages/react/src/components/FileUpload/FileUpload.module.css +++ b/packages/react/src/components/FileUpload/FileUpload.module.css @@ -44,6 +44,7 @@ text-align: center; cursor: pointer; overflow: hidden; + -webkit-user-select: none; user-select: none; transition: border-color var(--kernel-duration-enter) var(--kernel-ease-overlay), @@ -236,6 +237,7 @@ /* A drag gesture that starts on/near a file row shouldn't fight the browser's own text-selection heuristics — nothing in the row is meant to be selected as text. */ + -webkit-user-select: none; user-select: none; } diff --git a/packages/react/src/components/Label/Label.module.css b/packages/react/src/components/Label/Label.module.css index b56766b..f610bb7 100644 --- a/packages/react/src/components/Label/Label.module.css +++ b/packages/react/src/components/Label/Label.module.css @@ -4,6 +4,8 @@ font-size: var(--kernel-font-size-sm); font-weight: 500; color: var(--kernel-color-text); + -webkit-user-select: none; + user-select: none; } .required { diff --git a/packages/react/src/components/Nav/Nav.module.css b/packages/react/src/components/Nav/Nav.module.css index 7c60476..91d0a90 100644 --- a/packages/react/src/components/Nav/Nav.module.css +++ b/packages/react/src/components/Nav/Nav.module.css @@ -28,6 +28,8 @@ color: var(--kernel-color-text-muted); text-decoration: none; font-size: var(--kernel-font-size-sm); + -webkit-user-select: none; + user-select: none; transition: background-color var(--kernel-duration-fast) var(--kernel-ease-out), color var(--kernel-duration-fast) var(--kernel-ease-out); } diff --git a/packages/react/src/components/NavigationMenu/NavigationMenu.module.css b/packages/react/src/components/NavigationMenu/NavigationMenu.module.css index f51f870..a13626c 100644 --- a/packages/react/src/components/NavigationMenu/NavigationMenu.module.css +++ b/packages/react/src/components/NavigationMenu/NavigationMenu.module.css @@ -33,6 +33,7 @@ cursor: default; /* Same convention as Button/Checkbox/Switch: a clickable control's own label text never drag-selects. */ + -webkit-user-select: none; user-select: none; transition: background-color var(--kernel-duration-fast) var(--kernel-ease-out), color var(--kernel-duration-fast) var(--kernel-ease-out); diff --git a/packages/react/src/components/NumberField/NumberField.module.css b/packages/react/src/components/NumberField/NumberField.module.css index 8228f31..78131ca 100644 --- a/packages/react/src/components/NumberField/NumberField.module.css +++ b/packages/react/src/components/NumberField/NumberField.module.css @@ -153,6 +153,7 @@ cursor: pointer; /* Same convention as Button/Checkbox/Switch: a clickable control's own label text never drag-selects. */ + -webkit-user-select: none; user-select: none; transition: background-color var(--kernel-duration-fast) var(--kernel-ease-out), diff --git a/packages/react/src/components/Pagination/Pagination.module.css b/packages/react/src/components/Pagination/Pagination.module.css index 57545d7..27788a2 100644 --- a/packages/react/src/components/Pagination/Pagination.module.css +++ b/packages/react/src/components/Pagination/Pagination.module.css @@ -31,6 +31,7 @@ font-variant-numeric: tabular-nums; /* Same convention as Button/Checkbox/Switch: a clickable control's own label text never drag-selects. */ + -webkit-user-select: none; user-select: none; transition: background-color var(--kernel-duration-fast) var(--kernel-ease-out); } diff --git a/packages/react/src/components/RadioGroup/RadioGroup.module.css b/packages/react/src/components/RadioGroup/RadioGroup.module.css index 5b89507..91f957a 100644 --- a/packages/react/src/components/RadioGroup/RadioGroup.module.css +++ b/packages/react/src/components/RadioGroup/RadioGroup.module.css @@ -32,6 +32,8 @@ cursor: pointer; -webkit-user-select: none; user-select: none; + -webkit-touch-callout: none; + -webkit-tap-highlight-color: transparent; font-family: var(--kernel-font-sans); color: var(--kernel-color-text); } @@ -108,6 +110,7 @@ } .label { + -webkit-user-select: none; user-select: none; } diff --git a/packages/react/src/components/Reasoning/Reasoning.module.css b/packages/react/src/components/Reasoning/Reasoning.module.css index c8ccf24..4422b52 100644 --- a/packages/react/src/components/Reasoning/Reasoning.module.css +++ b/packages/react/src/components/Reasoning/Reasoning.module.css @@ -22,6 +22,7 @@ padding-block: var(--kernel-space-2); cursor: default; list-style: none; + -webkit-user-select: none; user-select: none; } @@ -80,6 +81,7 @@ .root[data-state="opening"] .content, .root[data-state="closing"] .content { overflow: clip; + -webkit-user-select: none; user-select: none; } diff --git a/packages/react/src/components/Resizable/Resizable.module.css b/packages/react/src/components/Resizable/Resizable.module.css index 06b8685..44438ae 100644 --- a/packages/react/src/components/Resizable/Resizable.module.css +++ b/packages/react/src/components/Resizable/Resizable.module.css @@ -15,6 +15,7 @@ } .root[data-dragging] { + -webkit-user-select: none; user-select: none; } @@ -28,6 +29,9 @@ position: relative; flex-shrink: 0; background-color: var(--kernel-color-border); + -webkit-user-select: none; + user-select: none; + -webkit-touch-callout: none; transition: background-color var(--kernel-duration-fast) var(--kernel-ease-out); } diff --git a/packages/react/src/components/Select/Select.module.css b/packages/react/src/components/Select/Select.module.css index 9640269..3d7e94f 100644 --- a/packages/react/src/components/Select/Select.module.css +++ b/packages/react/src/components/Select/Select.module.css @@ -40,6 +40,7 @@ font-size: var(--kernel-font-size-base); /* Same convention as Button/Checkbox/Switch: a clickable control's own label text never drag-selects. */ + -webkit-user-select: none; user-select: none; transition: border-color var(--kernel-duration-fast) var(--kernel-ease-out), scale var(--kernel-duration-fast) var(--kernel-ease-out); diff --git a/packages/react/src/components/Suggestion/Suggestion.module.css b/packages/react/src/components/Suggestion/Suggestion.module.css index a1a65cb..0038c8f 100644 --- a/packages/react/src/components/Suggestion/Suggestion.module.css +++ b/packages/react/src/components/Suggestion/Suggestion.module.css @@ -41,6 +41,7 @@ cursor: default; /* Same convention as Button/Checkbox/Switch: a clickable control's own label text never drag-selects. */ + -webkit-user-select: none; user-select: none; transition: background-color var(--kernel-duration-fast) var(--kernel-ease-out), diff --git a/packages/react/src/components/Switch/Switch.module.css b/packages/react/src/components/Switch/Switch.module.css index f52bbe8..950d7cb 100644 --- a/packages/react/src/components/Switch/Switch.module.css +++ b/packages/react/src/components/Switch/Switch.module.css @@ -6,6 +6,8 @@ cursor: pointer; -webkit-user-select: none; user-select: none; + -webkit-touch-callout: none; + -webkit-tap-highlight-color: transparent; font-family: var(--kernel-font-sans); color: var(--kernel-color-text); } @@ -93,6 +95,7 @@ } .label { + -webkit-user-select: none; user-select: none; } diff --git a/packages/react/src/components/Tabs/Tabs.module.css b/packages/react/src/components/Tabs/Tabs.module.css index 6964856..fae4503 100644 --- a/packages/react/src/components/Tabs/Tabs.module.css +++ b/packages/react/src/components/Tabs/Tabs.module.css @@ -56,6 +56,7 @@ cursor: default; /* Same convention as Button/Checkbox/Switch: a clickable control's own label text never drag-selects. */ + -webkit-user-select: none; user-select: none; transition: background-color var(--kernel-duration-fast) var(--kernel-ease-out), diff --git a/packages/react/src/components/TagInput/TagInput.module.css b/packages/react/src/components/TagInput/TagInput.module.css index 2ed9334..fb24ef1 100644 --- a/packages/react/src/components/TagInput/TagInput.module.css +++ b/packages/react/src/components/TagInput/TagInput.module.css @@ -80,6 +80,8 @@ opacity: 1; scale: 1; filter: blur(0); + -webkit-user-select: none; + user-select: none; transition: opacity var(--kernel-duration-exit-fast) linear, scale var(--kernel-duration-exit-fast) var(--kernel-ease-overlay), @@ -113,6 +115,7 @@ cursor: pointer; /* Same convention as Button/Checkbox/Switch: a clickable control's own label text never drag-selects. */ + -webkit-user-select: none; user-select: none; transition: opacity var(--kernel-duration-fast) var(--kernel-ease-out), diff --git a/packages/react/src/components/Toast/Toast.module.css b/packages/react/src/components/Toast/Toast.module.css index c4f5a04..a99c367 100644 --- a/packages/react/src/components/Toast/Toast.module.css +++ b/packages/react/src/components/Toast/Toast.module.css @@ -56,6 +56,7 @@ /* The whole toast is swipe-to-dismiss draggable; without this a drag gesture starting on its text selects that text instead of moving the toast. */ + -webkit-user-select: none; user-select: none; /* Collapsed default (depth 0, the front toast): no peek offset. Deeper @@ -270,6 +271,7 @@ cursor: default; /* Same convention as Button/Checkbox/Switch: a clickable control's own label text never drag-selects. */ + -webkit-user-select: none; user-select: none; opacity: 0.7; transition: opacity var(--kernel-duration-fast) var(--kernel-ease-out), diff --git a/packages/react/src/components/Toggle/Toggle.module.css b/packages/react/src/components/Toggle/Toggle.module.css index 57e91dd..c45b48b 100644 --- a/packages/react/src/components/Toggle/Toggle.module.css +++ b/packages/react/src/components/Toggle/Toggle.module.css @@ -10,7 +10,10 @@ cursor: default; /* Same convention as Button/Checkbox/Switch: a clickable control's own label text never drag-selects. */ + -webkit-user-select: none; user-select: none; + -webkit-touch-callout: none; + -webkit-tap-highlight-color: transparent; transition: background-color var(--kernel-duration-fast) var(--kernel-ease-out), color var(--kernel-duration-fast) var(--kernel-ease-out), diff --git a/packages/react/src/components/ToolCall/ToolCall.module.css b/packages/react/src/components/ToolCall/ToolCall.module.css index 180a251..a83b666 100644 --- a/packages/react/src/components/ToolCall/ToolCall.module.css +++ b/packages/react/src/components/ToolCall/ToolCall.module.css @@ -19,6 +19,7 @@ padding-block: var(--kernel-space-2); cursor: default; list-style: none; + -webkit-user-select: none; user-select: none; } @@ -147,6 +148,7 @@ .root[data-state="opening"] .content, .root[data-state="closing"] .content { overflow: clip; + -webkit-user-select: none; user-select: none; } diff --git a/packages/styles/src/reset.css b/packages/styles/src/reset.css index af3f78f..0e01ed2 100644 --- a/packages/styles/src/reset.css +++ b/packages/styles/src/reset.css @@ -94,22 +94,57 @@ } /* Every component already ships its own :hover/:active/:focus-visible - styling, so the browser's default tap flash and double-tap-to-zoom - delay only get in the way on touch devices. */ + styling, so the browser's default tap flash, long-press callout, and + double-tap-to-zoom delay only get in the way on touch devices. + + `label` is load-bearing: tapping a checkbox/radio/switch label + highlights the LABEL, not the input, so omitting it leaves the + grey iOS flash on the most common labelled controls. */ a, button, input, select, textarea, summary, + label, [role="button"], + [role="checkbox"], + [role="combobox"], [role="menuitem"], + [role="menuitemcheckbox"], + [role="menuitemradio"], + [role="option"], + [role="radio"], + [role="slider"], + [role="switch"], [role="tab"], - [role="switch"] { + [role="treeitem"] { touch-action: manipulation; -webkit-tap-highlight-color: transparent; } + /* Clickable chrome, not editable or copyable content. Links stay + selectable (copying the text is a real action) and keep the iOS + long-press callout (open in new tab / copy link). Inputs keep + selection because that's how you edit them. */ + button, + summary, + label, + [role="button"], + [role="checkbox"], + [role="menuitem"], + [role="menuitemcheckbox"], + [role="menuitemradio"], + [role="option"], + [role="radio"], + [role="switch"], + [role="tab"], + [role="treeitem"] { + -webkit-user-select: none; + user-select: none; + -webkit-touch-callout: none; + } + p, h1, h2, diff --git a/scripts/check-shape-pairing.mjs b/scripts/check-shape-pairing.mjs index 0e291a3..167e988 100644 --- a/scripts/check-shape-pairing.mjs +++ b/scripts/check-shape-pairing.mjs @@ -13,10 +13,14 @@ * * 2. Anything with `cursor: pointer` must also set `user-select: none`. * Otherwise a double-click on a disclosure selects its own label and a - * drag from a control selects text across the page. + * drag from a control selects text across the page. Any rule that sets + * `user-select: none` (or `text`) must also set `-webkit-user-select` to + * the same value — Safari still needs the prefix to actually suppress + * selection on a double-tap. * - * Native form controls are exempt from (2): there's no text in an `` to - * select. Anything else can opt out with an inline marker naming a reason: + * Native form controls are exempt from the cursor:pointer half of (2): + * there's no text in an `` to select. Anything else can opt out + * with an inline marker naming a reason: * * /* shape-pairing-ok: reason *\/ * @@ -115,6 +119,13 @@ for (const { dir, suffix } of SOURCES) { } for (const { selector, body } of rules(css)) { + const userSelect = body.match(/user-select:\s*(none|text)/); + if (userSelect && !new RegExp(`-webkit-user-select:\\s*${userSelect[1]}`).test(body)) { + failures.push( + `${relative}: \`${selector}\` sets user-select: ${userSelect[1]} ` + + `without -webkit-user-select (see AGENTS.md).`, + ); + } if (!/cursor:\s*pointer/.test(body)) continue; if (/user-select:\s*none/.test(body)) continue; if (isNativeControl(selector, body)) continue;