Skip to content

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 .cover overlay (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-filter in addition to the unprefixed property. The unprefixed-only declaration is silently dropped. Firefox ESR/older builds simply don't support backdrop-filter.
  • Fix: Add the prefix and a colour fallback for browsers without backdrop-filter:
    .cover {
      background-color: rgba(255, 255, 255, 0.5); /* fallback */
      -webkit-backdrop-filter: blur(12px);
      backdrop-filter: blur(12px);
    }
    
  • 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 transform shorthand. 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 regular transform: 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 transform shorthand. For question-node:
    .drag-indicator { transform: translate(0, -50%) rotate(90deg); }
    
  • 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: clip was added in Safari 16. Earlier versions ignore the declaration entirely (no graceful fallback to hidden).
  • Fix: Use overflow-y: hidden (the existing intent — this admin surface doesn't need to allow position-sticky descendants). Or add a fallback:
    .template-item { overflow-y: hidden; overflow-y: clip; }
    
  • 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 rule input: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 remains display: block with whatever default styling Material produces.
  • Fix: Move the rule from a :has() global to the component template itself (e.g. add a display: none class 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:
    @supports selector(:has(*)) {
      input:has(+ mat-checkbox) { display: none !important; }
    }
    
    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 100vh ends 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 100vh as the largest possible viewport (toolbar collapsed). When the toolbar is showing the visible area is smaller. The fix is the dynamic viewport unit dvh.
  • Fix: app.component.scss already uses the correct pattern (min-height: 100vh; min-height: 100svh; at lines 16-17). Apply the same pattern to these two files:
    .candidate-container { height: 100vh; height: 100dvh; }
    .tree-container { max-height: 100vh; max-height: 100dvh; }
    
  • 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-box display.
  • Severity: minor
  • Symptom: In modern Firefox (≥ 68) -webkit-line-clamp works 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-box recipe; the standard line-clamp property added in 2023 is not paired alongside.
  • Fix: Add the standard line-clamp and a max-height fallback:
    display: -webkit-box;
    -webkit-line-clamp: 4;
    line-clamp: 4;
    -webkit-box-orient: vertical;
    overflow: hidden;
    
    The current code already uses 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: auto containers. 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-sticky line from contact-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 builds display: none on ::marker was historically ignored — the supported value is content: none.
  • Cause: Browser quirk in older Firefox; current Gecko (≥ 86) handles display: none on ::marker correctly.
  • 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 impersonation type="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-path since 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:
    -webkit-clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%);
    clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%);
    
  • 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 FILL on 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' 1 axis. This is mitigated by also applying the mat-icon-filled modifier or using the Material Symbols Filled font 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, 31
  • src/app/project/project-admin/study-partitions/study-partitions.component.html:16
  • src/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:158 does include ::-webkit-inner-spin-button { height: auto; } but no appearance: textfield reset.
  • Fix: If the design intent is "no spinner" (likely, given Material formfield), add a global rule:
    input[type='number'] { -moz-appearance: textfield; appearance: textfield; }
    input[type='number']::-webkit-inner-spin-button,
    input[type='number']::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }
    
  • Effort: low

F14. flex-layout (fxFlex, fxLayout) — 452 references; framework deprecated

  • Files: src/app/**/*.html and 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 gap adoption decisions.
  • Severity: minor
  • Symptom: @angular/flex-layout is deprecated since Angular 14 (2022) and abandoned. It pre-dates flex gap (which Safari only got in 14.1 / iOS 14.5) and emits its own margin-based spacing for fxLayoutGap. So today the project is paying for this twice — both flex-layout's emitted styles AND modern gap: 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 / fxLayout to native flex/grid as a separate workstream. No specific Safari/Firefox rendering bug today, but gap in 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, 138
  • src/app/info/home/whats-new/whats-new.component.scss:40, 66, 104, 139, 224, 375, 402, 417, 418
  • src/app/project/data-export/shared/data-export-layout/data-export-layout.component.scss:32, 48, 72, 109, 140, 181
  • src/app/core/components/version-check-dialog/version-check-dialog.component.scss:28, 60, 66, 144, 181, 197
  • src/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-login form, the version-check-dialog controls, and the data-export-layout step layout.
  • Cause: Flex gap shipped 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 browserslist baseline includes Safari ≥ 14.1, no action needed. Otherwise, fall back to margins. Verify the browserslist configuration (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 for transform since 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(...) and transform-origin: 0 0.
  • Effort: trivial

F17. Bootstrap legacy CSS imported globally still references -webkit- only properties

  • File: src/global-styles/styles.scss:1-4 (imports legacy-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, .progress classes 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 @keyframes for years. The four prefixed copies (-webkit-, -moz-, -ms-, -o-) are dead weight.
  • Cause: Legacy authoring (~2014 era).
  • Fix: Reduce to the unprefixed form only:
    img#syrfLogo {
      /* ... */
      animation: fadein 3.5s ease-in-out;
    }
    @keyframes fadein {
      from { opacity: 0; }
      to { opacity: 1; }
    }
    
  • Effort: trivial

Notes / Patterns Observed

  1. No ::-webkit-scrollbar styling anywhere. SyRF relies entirely on the OS default scrollbar — no Firefox/WebKit divergence to worry about. Good.
  2. No @supports blocks anywhere in app code. Adding feature gates around :has() and backdrop-filter would harden critical paths (F1, F4) without removing functionality on supported browsers.
  3. Inline style="" is widely used (~60 occurrences across ~40 templates). Most are simple width, margin, color and don't introduce browser issues. None of them use vendor-specific properties.
  4. @angular/flex-layout (fxFlex etc.) is used 452 times. This is a deprecated framework. Its retention pre-dates gap and display: grid which would be more portable. See F14 / F15.
  5. 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 :host first — no risk of var() resolving to nothing.
  6. backdrop-filter is used exactly once. F1 is the only blocking finding for Safari rendering.
  7. The codebase mixes pre-modern (-moz-keyframes, IE-targeted -ms-touch-action) and modern (100svh, individual translate: 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.