diff --git a/frontend/src/components/Editor.svelte b/frontend/src/components/Editor.svelte
index 0122277a83..ed8339ef81 100644
--- a/frontend/src/components/Editor.svelte
+++ b/frontend/src/components/Editor.svelte
@@ -233,7 +233,6 @@
height: 100%;
background: var(--color-2-mildblack);
overscroll-behavior: none;
- -webkit-user-select: none; // Still required by Safari as of 2025
user-select: none;
}
diff --git a/frontend/src/components/floating-menus/Dialog.svelte b/frontend/src/components/floating-menus/Dialog.svelte
index 4e5e0da385..3b2ca07d4d 100644
--- a/frontend/src/components/floating-menus/Dialog.svelte
+++ b/frontend/src/components/floating-menus/Dialog.svelte
@@ -136,7 +136,6 @@
}
.text-label.multiline {
- -webkit-user-select: text; // Still required by Safari as of 2026
user-select: text;
}
diff --git a/frontend/src/components/panels/Layers.svelte b/frontend/src/components/panels/Layers.svelte
index 5b5a393dcc..a9995954f3 100644
--- a/frontend/src/components/panels/Layers.svelte
+++ b/frontend/src/components/panels/Layers.svelte
@@ -884,7 +884,6 @@
width: 100%;
&:disabled {
- -webkit-user-select: none; // Still required by Safari as of 2025
user-select: none;
// Workaround for `user-select: none` not working on elements
pointer-events: none;
diff --git a/frontend/src/components/views/Graph.svelte b/frontend/src/components/views/Graph.svelte
index 1bce166f6e..773c59f5df 100644
--- a/frontend/src/components/views/Graph.svelte
+++ b/frontend/src/components/views/Graph.svelte
@@ -952,7 +952,6 @@
&.faded:hover {
z-index: 2;
opacity: 1;
- -webkit-user-select: text;
user-select: text;
transition:
opacity 0.2s,
diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts
index 241df61326..8a0adc56c3 100644
--- a/frontend/vite.config.ts
+++ b/frontend/vite.config.ts
@@ -11,7 +11,7 @@ const projectRootDir = path.resolve(__dirname);
// https://vitejs.dev/config/
export default defineConfig(({ mode }) => {
return {
- plugins: [svelteGlobalStyles(), svelte(), staticAssets(), mode !== "native" && thirdPartyLicenses(), mode !== "native" && serviceWorker()],
+ plugins: [svelteGlobalStyles(), webkitUserSelectPrefix(), svelte(), staticAssets(), mode !== "native" && thirdPartyLicenses(), mode !== "native" && serviceWorker()],
resolve: {
alias: [{ find: /\/..\/branding\/(.*\.svg)/, replacement: path.resolve(projectRootDir, "../branding", "$1?raw") }],
},
@@ -35,6 +35,31 @@ function svelteGlobalStyles(): PluginOption {
};
}
+// Adds the `-webkit-user-select` prefix alongside every `user-select` declaration in Svelte component styles (still required by Safari). Remove when Safari ships the unprefixed version.
+// WebKit tracking issue:
+// https://bugs.webkit.org/show_bug.cgi?id=208677
+// Included in Interop 2026:
+// https://webkit.org/blog/17818/announcing-interop-2026/#web-compat
+// https://github.com/web-platform-tests/interop/issues/1000#issuecomment-3892214470
+// Web platform test for WebKit implementation status:
+// https://wpt.fyi/results/css/css-ui/parsing/user-select-computed.html?label=master&label=experimental&aligned&view=interop&q=label%3Ainterop-2026-webcompat
+function webkitUserSelectPrefix(): PluginOption {
+ return {
+ name: "webkit-user-select-prefix",
+ enforce: "pre",
+ transform(code, id) {
+ if (!id.endsWith(".svelte")) return;
+
+ return code.replace(/`;
+ });
+ },
+ };
+}
+
function staticAssets(): PluginOption {
const STATIC_ASSET_DIRS: { source: string; urlPrefix: string }[] = [
{ source: "../demo-artwork", urlPrefix: "/demo-artwork" },