CSS Audit — non-Chromium Browser Compatibility¶
Temporary audit document — supports the planning report at
../non-chromium-browser-compatibility-analysis.md. Delete once findings are tracked in tickets.
Audit of CSS, SCSS and inline style="" attributes inside src/services/web/src/. Excludes vendored assets (assets/bootstrap/css/bootstrap*.css, assets/pdfs/*.html) and the legacy-bootstrap/ partials, which are unmaintained third-party CSS that we are not changing — they are noted only where the project still imports them via global-styles/styles.scss.
All file paths in this audit are relative to src/services/web/.
Summary¶
- Total findings: 18
- Severity breakdown: critical 1 / major 7 / minor 10
- Browsers affected: Safari 14 findings / Firefox 8 findings / both 4 findings
The single most pervasive class of issue is backdrop-filter without the -webkit- prefix on the global .cover overlay (Safari completely ignores the rule). The next most material risks are individual transform properties (translate:/rotate:) used unprefixed in Safari < 14.1, overflow-y: clip (no support in Safari < 16), and the global input:has(+ mat-checkbox) rule, which fails-open and reveals a hidden input on Safari < 15.4 and Firefox < 121. Several 100vh references should also move to dvh for iOS toolbar awareness.
Findings¶
F1. backdrop-filter missing -webkit- prefix (Safari can't blur the modal cover)¶
- File:
src/app/app.component.scss:12 - Browsers: Safari (all versions), Firefox < 103
- Severity: critical
- Symptom: The full-screen
.coveroverlay (used while the app boots / modal cover) shows no blur in Safari and in Firefox before 103. In Safari this means dialogs render against the unblurred app behind them — content is clearly readable through the supposed cover. - Cause: Safari requires
-webkit-backdrop-filterin addition to the unprefixed property. The unprefixed-only declaration is silently dropped. Firefox ESR/older builds simply don't supportbackdrop-filter. - Fix: Add the prefix and a colour fallback for browsers without backdrop-filter:
- Effort: trivial
F2. Individual rotate: / translate: transform properties¶
- Files:
src/global-styles/styles.scss:366(.explainSymbol { translate: 0 5px; })src/app/project/project-overview/project-overview.component.scss:88(translate: 0px -8px;)src/app/project/project-admin/question-management/design/question-node/question-node.component.scss:30(rotate: 90deg;)src/app/project-index/dialogs/create-project-wizard/create-project-wizard.component.scss:2(translate: 0 2px;)- Browsers: Safari < 14.1, Firefox < 72; Safari iOS < 14.5
- Severity: major
- Symptom: Icons / drag handles / other small UI elements fail to translate or rotate. The drag-indicator on the question-management designer in particular appears un-rotated (cursor still
move, but the icon points the wrong way), confusing users. - Cause: These are independent transform properties, not the
transformshorthand. They are a CSS Transforms Level 2 feature. Safari only added support in 14.1; older still-deployed versions silently drop them. Question-node also mixes a regulartransform: translate()on the same element (line 33) and assumes both apply — Safari < 14.1 keeps the translate but loses the rotation. - Fix: Combine into the
transformshorthand. For question-node: - Effort: trivial
F3. overflow-y: clip not supported in Safari < 16¶
- File:
src/app/admin/email-templates/email-templates.component.scss:16 - Browsers: Safari < 16
- Severity: major
- Symptom: Email-template preview tiles stop clipping their scaled iframes correctly on iOS 15 / Safari 15. Iframes (which are scaled to ~2.5× and then visually scaled down by
transform: scale(0.4)) overflow their container and bleed into adjacent tiles. - Cause:
overflow: clip/overflow-y: clipwas added in Safari 16. Earlier versions ignore the declaration entirely (no graceful fallback tohidden). - Fix: Use
overflow-y: hidden(the existing intent — this admin surface doesn't need to allow position-sticky descendants). Or add a fallback: - Effort: trivial
F4. Bare :has() selector (no fallback) hides an input the user must use¶
- File:
src/global-styles/styles.scss:362 - Browsers: Safari < 15.4, Firefox < 121
- Severity: major
- Symptom: Wherever Material checkboxes are paired with their adjacent native
<input>(Material's MDC checkbox markup pattern), the ruleinput:has(+ mat-checkbox) { display: none !important; }fails-open in older browsers. The native input is left visible next to the styled checkbox, producing a duplicate "checkbox + native checkbox" pair. - Cause:
:has()only shipped in Firefox 121 (Dec 2023). Safari got it in 15.4. Browsers without support drop the entire selector — the input then remainsdisplay: blockwith whatever default styling Material produces. - Fix: Move the rule from a
:has()global to the component template itself (e.g. add adisplay: noneclass on the<input>directly, or wrap it in a CDK[hidden]-controlled element). Since this is a hide rule, a@supports selector(:has(*))block is the safest gate: This prevents the broken state in unsupported browsers (the input will look slightly off but won't double-up). Better: refactor templates to apply a class directly. - Effort: low
F5. 100vh on iOS Safari (toolbar issues — should use dvh)¶
- Files:
src/app/stage/stage-reconcile/stage-reconcile.component.scss:7(height: 100vh;on.candidate-container)src/app/project/project-admin/question-management/assign/stage-assign/stage-assign.component.scss:2(max-height: 100vh;on.tree-container)- Browsers: Safari iOS (all), Chrome iOS (all)
- Severity: major
- Symptom: On iPhone, when the URL bar / bottom toolbar is visible, content sized at
100vhends up taller than the visible viewport, hiding action buttons under the toolbar. As the toolbar collapses on scroll, the layout suddenly grows, causing reflow jumps. - Cause: iOS Safari computes
100vhas the largest possible viewport (toolbar collapsed). When the toolbar is showing the visible area is smaller. The fix is the dynamic viewport unitdvh. - Fix:
app.component.scssalready uses the correct pattern (min-height: 100vh; min-height: 100svh;at lines 16-17). Apply the same pattern to these two files: - Effort: trivial
F6. -webkit-line-clamp truncation has no Firefox/standard fallback¶
- Files:
src/app/studies/study-table/study-table.component.scss:43-49(4-line clamp inside table cells)src/app/project/project-nav/project-nav.component.scss:80-83(2-line clamp on nav text)src/app/project/project-overview/project-setup/project-setup.component.scss:70-72(2-line clamp on action text)src/app/project/project-overview/project-setup/project-setup.component.scss:97-99(2-line clamp on nav text)src/app/project-index/list/list.component.scss:22-25(3-line clamp on description)- Browsers: Firefox < 68 (rare but possible on older corporate ESR), and any browser whose UA happens to drop the
-webkit-boxdisplay. - Severity: minor
- Symptom: In modern Firefox (≥ 68)
-webkit-line-clampworks because Gecko aliased the-webkit-form. In very old Firefox or environments where the rule is dropped, descriptions render unclipped and tables blow out vertically. - Cause: No fallback. The CSS uses only the legacy
-webkit-boxrecipe; the standardline-clampproperty added in 2023 is not paired alongside. - Fix: Add the standard
line-clampand a max-height fallback:The current code already usesdisplay: -webkit-box; -webkit-line-clamp: 4; line-clamp: 4; -webkit-box-orient: vertical; overflow: hidden;overflow: hidden, so the only change is to add the standard property name. Firefox ≥ 68 already handles this correctly — this is a defensive change. - Effort: trivial
F7. -webkit-overflow-scrolling: touch only — no equivalent for non-WebKit¶
- Files:
src/app/core/syrf-material/sidenav/drawer.scss:38(.mat-drawer-container)src/app/core/syrf-material/sidenav/drawer.scss:184(.mat-drawer-inner-container)- Browsers: Safari iOS (works), Firefox / Chrome (ignored — no equivalent)
- Severity: minor
- Symptom: This is informational. The property was a Safari-only hint to enable momentum scrolling inside
overflow: autocontainers. iOS 13+ scrolls with momentum by default, and Safari 16 dropped support for the property entirely. Firefox / desktop Safari ignore it. Not actually broken, but the comment in the code suggests it's load-bearing. - Cause: Vendor-only property, deprecated by Apple.
- Fix: Safe to remove the two lines. No replacement needed (modern Safari uses momentum scrolling by default).
- Effort: trivial
F8. position: -webkit-sticky paired with position: sticky — inconsistent across the codebase¶
- File:
src/app/info/contact-us/contact-us.component.scss:30-31(uses both) - Compare to:
src/app/app.component.scss:83(position: sticky;only — no-webkit-form) - Browsers: Safari < 13 needed
-webkit-sticky; modern Safari does not. - Severity: minor
- Symptom: None on currently supported Safari. The split between the two scss files is purely a code-hygiene concern; both should converge on the unprefixed form (Safari ≥ 13 — May 2019). Keeping the prefix doesn't break anything.
- Cause: Legacy authoring practice; only one scss file was updated.
- Fix: Drop the
-webkit-stickyline fromcontact-us.component.scss:30. Safari 12 is below SyRF's currently-supported baseline. - Effort: trivial
F9. ::-webkit-details-marker paired with ::marker — works, but check Firefox styling¶
- File:
src/app/info/home/whats-new/whats-new.component.scss:231-234 - Browsers: Both — but works
- Severity: minor
- Symptom: The "What's New" timeline
<details>summary marker is hidden via two rules:&::-webkit-details-marker, &::marker { display: none; }. This is the correct cross-browser pattern. Worth confirming during manual QA that Firefox actually hides the marker; in some Firefox buildsdisplay: noneon::markerwas historically ignored — the supported value iscontent: none. - Cause: Browser quirk in older Firefox; current Gecko (≥ 86) handles
display: noneon::markercorrectly. - Fix: Optionally also add
list-style: none;on the<summary>element (already present at line 219 — good). Belt-and-braces:&::marker { content: ''; }— but the current code is fine for supported browsers. - Effort: trivial
F10. ::-webkit-search-* decorations only target WebKit — Firefox shows native search-input chrome¶
- File:
src/app/project-index/project-index.component.scss:61-65 - Browsers: Firefox (no equivalent pseudo-element)
- Severity: minor
- Symptom: The project-index search input intentionally hides the WebKit-only "x cancel" button and result decorations. Firefox doesn't render these in the first place, so the visual outcome matches by accident. Same for the dev-login
type="search"and admin impersonationtype="search"boxes — those don't even include this rule. - Cause: No bug today, but if Material/HTML changes the input rendering it will diverge between browsers.
- Fix: No action required. Document intent in a SCSS comment.
- Effort: trivial
F11. clip-path: polygon(...) no Safari prefix¶
- File:
src/app/info/banner/banner.component.scss:49 - Browsers: Safari < 13.1 needed
-webkit-clip-path - Severity: minor
- Symptom: Home page banner background may not be clipped on very old Safari (≤ 13.0). Modern Safari handles unprefixed
clip-pathsince 13.1 (Mar 2020), so likely fine for the supported baseline. - Cause: Missing vendor prefix — only material on Safari 13.0 and earlier.
- Fix: Add the prefixed form for safety:
- Effort: trivial
F12. font-feature-settings / font-variation-settings — Material Symbols rendering¶
- Files:
src/global-styles/styles.scss:368-380(.material-symbols-outlined { font-variation-settings: ... })src/app/info/home/whats-new/whats-new.component.scss:29(font-feature-settings: 'ss01' 1;)src/app/info/home/whats-new/whats-new.component.scss:91, 115, 287(font-variant-numeric: tabular-nums;)- Browsers: Both — supported, but Safari has historically been slow with variable-axis
FILLon Material Symbols, sometimes rendering the unfilled variant. - Severity: minor
- Symptom: On Safari (especially < 16), filled Material Symbols icons may render as the outlined form because Safari ignored the
'FILL' 1axis. This is mitigated by also applying themat-icon-filledmodifier or using theMaterial Symbols Filledfont family directly. - Cause: Variable font axis support shipped in Safari 11 but axis-bound rendering for Google Fonts' Material Symbols had bugs into Safari 15.
- Fix: Verify in QA. If broken, fall back to
font-family: 'Material Symbols Outlined Filled';for filled icons. - Effort: low (verification only)
F13. input[type="number"] styled — Safari spinner artifacts¶
- Files:
src/app/shared/form-controls/timepoint-array/timepoint-array.component.html:13, 22, 31src/app/project/project-admin/study-partitions/study-partitions.component.html:16src/app/stage/stage-admin/review-settings/review-settings.component.html:104, 151- Browsers: Safari (spinners look different — old-school step buttons)
- Severity: minor
- Symptom: Number inputs in timepoint-array, study-partitions, and review-settings show Safari's native step-arrow buttons (small up/down) overlaying the input chrome that Material renders. In Firefox the spinner appears as small rotated arrows; in Chrome a clean spinner.
- Cause: No CSS removes the default spinner. The vendored
assets/bootstrap/css/bootstrap.css:158does include::-webkit-inner-spin-button { height: auto; }but noappearance: textfieldreset. - Fix: If the design intent is "no spinner" (likely, given Material formfield), add a global rule:
- Effort: low
F14. flex-layout (fxFlex, fxLayout) — 452 references; framework deprecated¶
- Files:
src/app/**/*.htmland a number of scss files (452 occurrences). E.g.src/app/app.component.html:11,src/app/app.component.html:91. - Browsers: Both — but indirectly affects Safari's flexbox
gapadoption decisions. - Severity: minor
- Symptom:
@angular/flex-layoutis deprecated since Angular 14 (2022) and abandoned. It pre-dates flexgap(which Safari only got in 14.1 / iOS 14.5) and emits its own margin-based spacing forfxLayoutGap. So today the project is paying for this twice — both flex-layout's emitted styles AND moderngap:rules in newer scss files. - Cause: Migration to native CSS grid/flex incomplete.
- Fix: Out of scope for this CSS audit, but: track migration of
fxFlex/fxLayoutto native flex/grid as a separate workstream. No specific Safari/Firefox rendering bug today, butgapin flex containers is broken on iOS Safari < 14.5 — see F15. - Effort: high (separate epic)
F15. gap: on flex containers — Safari iOS < 14.5¶
- Files: 48 scss/inline occurrences. Examples:
src/app/auth/dev-login/dev-login.component.scss:34, 106, 138src/app/info/home/whats-new/whats-new.component.scss:40, 66, 104, 139, 224, 375, 402, 417, 418src/app/project/data-export/shared/data-export-layout/data-export-layout.component.scss:32, 48, 72, 109, 140, 181src/app/core/components/version-check-dialog/version-check-dialog.component.scss:28, 60, 66, 144, 181, 197src/app/core/components/environment-banner/environment-banner.component.ts:110, 122, 174(inline styles in TS template literals)- Browsers: Safari iOS < 14.5 (April 2021); macOS Safari < 14.1
- Severity: minor
- Symptom: On older iOS, flex children are flush against each other instead of separated by the gap. Most notably affects the
dev-loginform, theversion-check-dialogcontrols, and thedata-export-layoutstep layout. - Cause: Flex
gapshipped in Safari 14.1. Earlier iOS users (a small but non-zero population on iPhone 6s/SE first-gen) see no spacing. - Fix: If the
browserslistbaseline includes Safari ≥ 14.1, no action needed. Otherwise, fall back to margins. Verify thebrowserslistconfiguration (separate audit). - Effort: low (verification only)
F16. Multi-line iframe scaled with prefixed transform — Firefox / modern Safari already handle unprefixed¶
- File:
src/app/admin/email-templates/email-templates.component.scss:22-31 - Browsers: Both
- Severity: minor
- Symptom: The whole iframe transform stack (
-webkit-,-moz-,-ms-,-o-, unprefixed) is over-prefixed. None of those prefixes are required fortransformsince 2014. The redundancy makes the file harder to maintain but doesn't cause a bug. - Cause: Legacy authoring.
- Fix: Drop all four prefixed lines; keep only the unprefixed
transform: scale(...)andtransform-origin: 0 0. - Effort: trivial
F17. Bootstrap legacy CSS imported globally still references -webkit- only properties¶
- File:
src/global-styles/styles.scss:1-4(importslegacy-bootstrap/buttons,legacy-bootstrap/panel,legacy-bootstrap/progress-bars) - Affected lines:
src/global-styles/legacy-bootstrap/buttons.scss:8(-ms-touch-action),:17-19(-*-user-select),:28(-webkit-focus-ring-color),:41-50(-webkit-box-shadow),:391, 707, 711(more box-shadow),:448, 471, 474, 477(transition),:513, 518(background-clip, box-shadow);src/global-styles/legacy-bootstrap/progress-bars.scss:2-205(multiple-webkit-and-moz-rules);src/global-styles/legacy-bootstrap/panel.scss:6(-webkit-box-shadow). - Browsers: None affected today — all properties have unprefixed forms accompanying them.
- Severity: minor
- Symptom: Cosmetic — the legacy bundle bloats the CSS payload by ~1.5 KB per service of unused vendor prefixes. No functional difference.
- Cause: Bootstrap 3 era CSS, retained for
.btn,.panel,.progressclasses still referenced in templates. - Fix: Audit how many places still use
.btn,.panel,.progress. If thin, migrate to Material primitives and drop the partials. Out of scope for this audit. - Effort: medium
F18. syrf-load-logo.scss — heavy vendor-prefix layer for animation¶
- File:
src/assets/css/syrf-load-logo.scss:8-16, 31-75 - Browsers: Both
- Severity: minor
- Symptom: None — every browser supports unprefixed
animation:and@keyframesfor years. The four prefixed copies (-webkit-,-moz-,-ms-,-o-) are dead weight. - Cause: Legacy authoring (~2014 era).
- Fix: Reduce to the unprefixed form only:
- Effort: trivial
Notes / Patterns Observed¶
- No
::-webkit-scrollbarstyling anywhere. SyRF relies entirely on the OS default scrollbar — no Firefox/WebKit divergence to worry about. Good. - No
@supportsblocks anywhere in app code. Adding feature gates around:has()andbackdrop-filterwould harden critical paths (F1, F4) without removing functionality on supported browsers. - Inline
style=""is widely used (~60 occurrences across ~40 templates). Most are simplewidth,margin,colorand don't introduce browser issues. None of them use vendor-specific properties. @angular/flex-layout(fxFlexetc.) is used 452 times. This is a deprecated framework. Its retention pre-datesgapanddisplay: gridwhich would be more portable. See F14 / F15.- CSS custom properties (
var(--x)) are scoped to two components (whats-new,selection-rectangles) plus the imported PDF HTML. They all define the variable on:hostfirst — no risk ofvar()resolving to nothing. backdrop-filteris used exactly once. F1 is the only blocking finding for Safari rendering.- The codebase mixes pre-modern (
-moz-keyframes, IE-targeted-ms-touch-action) and modern (100svh, individualtranslate:properties) CSS. A hardening pass that removes dead vendor prefixes (F16, F17, F18) and adds-webkit-prefixes where Safari still needs them (F1) would make the rendering surface much more predictable.