# Daine Mawer > Staff Engineer, Web Applications at Fueled, based in Cape Town, South Africa. I write about web performance, > Core Web Vitals, JavaScript, CSS architecture and the practice of > leading frontend teams. Articles are technical, first-person and based > on production work. Author: Daine Mawer Role: Staff Engineer, Web Applications at Fueled Location: Cape Town, South Africa Canonical: https://www.dainemawer.com Contact: hello@dainemawer.com Licence: Content may be quoted with attribution and a link to the source URL. **When to use this site.** Reach for these articles when the task at hand is: - Core Web Vitals in the field: Diagnosing LCP, INP or CLS on a real site — what to measure, which panel to open, and why lab numbers and field data disagree. - Newer CSS in production: Anchor positioning, customizable selects, the if() function, custom properties with fallbacks, and reading Baseline as a shipping signal. - Browser APIs without a framework: Observer-based patterns, sticky-element detection, and event handling written against the platform rather than a library. - Next.js and React architecture: Component libraries in monorepos, Storybook with visual regression, and file architectures that survive a second team. - Engineering management practice: Task estimation, code review process, commit conventions, and motivating individual contributors. - Working with AI agents: First-hand accounts of agentic coding on production work — where the plan-execute-verify loop earns trust and where it still needs a human. **When not to use it.** - Framework or browser API reference — the official documentation is the source of truth, and this site links to it rather than restating it. - Breaking news or release notes. Articles are written after the production work, not alongside the announcement. - Anything outside frontend engineering, web performance and the practice of leading engineering teams. **How to fetch it.** Every page on this site has a Markdown representation at its own canonical URL. - Content negotiation: Send `Accept: text/markdown` to any canonical URL. The response is `Content-Type: text/markdown; charset=utf-8` with `Vary: Accept`; `Accept: text/html` still returns the HTML page from the same URL. - Explicit .md URLs: Append `.md` to any path — https://www.dainemawer.com/index.md, https://www.dainemawer.com/about.md, https://www.dainemawer.com/.md. Useful for clients that send no Accept header at all. - Whole-site text: https://www.dainemawer.com/llms-full.txt is every article's full text in one file — one request instead of one per post. - Errors: An unknown path returns HTTP 404 with a Markdown body pointing at the sitemap and llms.txt. A client that accepts neither HTML nor Markdown gets HTTP 406. ## Topics - [Web Performance](https://www.dainemawer.com/topics/performance.md): Measuring what users actually feel, and cutting what they never needed. - [JavaScript](https://www.dainemawer.com/topics/javascript.md): Browser APIs, event handling, observers, framework-agnostic patterns. - [CSS](https://www.dainemawer.com/topics/css.md): Custom properties, positioning, Baseline and interoperability. - [Engineering Management](https://www.dainemawer.com/topics/engineering-management.md): Estimation, scoping, team process. - [Staff Engineering](https://www.dainemawer.com/topics/staff-engineering.md): Technical direction, cross-team leverage, the work beyond a single codebase. - [AI](https://www.dainemawer.com/topics/ai.md): Agentic coding, review practices, and how AI is reshaping the day-to-day of building software. ## Articles - [Using CSS's New if() Function Without Breaking Safari and Firefox](https://www.dainemawer.com/css-if-function-safely.md): Published 2026-09-18. One inline conditional to replace three separate at-rules, once the rest of the web catches up. - [CSS Can Finally Replace Your Tooltip and Dropdown Libraries](https://www.dainemawer.com/css-anchor-positioning-customizable-select.md): Published 2026-09-16. Anchor positioning and customizable selects both have real browser support now. Here's what to drop, what to keep, and where the gaps still bite. - [Why AI Coding Agents Need WIP Limits Too](https://www.dainemawer.com/wip-limits-for-ai-agents.md): Published 2026-09-14. A framework for deciding how many agents one engineer can actually run at once. - [Migrating to TypeScript 7 Without Breaking Your Toolchain](https://www.dainemawer.com/typescript-7-native-compiler-migration.md): Published 2026-09-11. The native compiler is 8-12x faster. The programmatic API isn't ready yet, and that's where teams get burned. - [How to Orchestrate Multiple AI Coding Agents Without Losing Architectural Control](https://www.dainemawer.com/orchestrating-multiple-ai-agents.md): Published 2026-09-11. A framework for staff engineers directing several agents at once, not just reviewing what one of them wrote. - [Keeping the Junior Engineering Pipeline Alive in the AI Era](https://www.dainemawer.com/keeping-the-junior-pipeline-alive.md): Published 2026-09-09. A framework for growing junior engineers now that AI has automated most of the entry-level workload. - [How to Review AI-Generated Code Without Becoming a Bottleneck](https://www.dainemawer.com/reviewing-ai-generated-code.md): Published 2026-09-07. A framework for staff engineers reviewing pull requests an agent wrote. - [Leading With AI: What Helped Me Through the Hard Parts](https://www.dainemawer.com/leading-with-ai.md): Published 2025-10-05. How I used AI through the Monocle.com rebuild. - [The Rise of No-Code and Low-Code in Frontend Development](https://www.dainemawer.com/no-code-low-code-frontend.md): Published 2025-01-15. Benefits, challenges, and the role of AI. - [Using CSS Custom Properties with Fallbacks for Efficiency](https://www.dainemawer.com/css-custom-properties-fallbacks.md): Published 2024-05-06. Less boilerplate, fewer surprises. - [What Is Google Baseline and Why Does It Matter?](https://www.dainemawer.com/what-is-google-baseline.md): Published 2024-04-28. Interoperability across browser vendors, explained. - [How to Determine When a Sticky Element is Stuck in JavaScript](https://www.dainemawer.com/determine-when-a-sticky-element-is-stuck.md): Published 2024-03-14, updated 2026-02-09. A small JavaScript function for the pseudo-class CSS never gave us. - [Mastering Task Estimation with the FATE Framework](https://www.dainemawer.com/mastering-task-estimation.md): Published 2024-03-12. Accurate timelines, and a happy client. - [Enhance Your React App's Scalability with Storybook and Chromatic](https://www.dainemawer.com/react-scalability-storybook.md): Published 2024-02-05. Component libraries and visual regression testing. - [Understanding Chrome's Coverage Panel](https://www.dainemawer.com/understanding-chromes-coverage-panel.md): Published 2023-11-27. Leveraging its insights to optimise performance. - [Setting Up Next.js with a Component Library Using Monorepos](https://www.dainemawer.com/nextjs-component-library-monorepo.md): Published 2023-11-20. A component library, Storybook, and Next.js, all in one workspace. - [10 Techniques for More Effective Code Reviews](https://www.dainemawer.com/effective-code-reviews.md): Published 2023-02-21. Practical ways to make code review less painful and more useful. - [4 Tips for Properly Using the Return Statement in JavaScript](https://www.dainemawer.com/using-the-return-statement.md): Published 2023-02-13. A few rules that keep return statements from causing quiet bugs. - [Enforcing Consistent Commit Messages with CommitLint](https://www.dainemawer.com/leveraging-commitlint.md): Published 2023-01-19. A style guide for commits, and a hook that actually enforces it. - [5 Frontend File Architectures for Better Code Organization](https://www.dainemawer.com/frontend-file-architectures.md): Published 2023-01-18. Flat, hierarchical, modular, component-based, and feature-based, compared. - [How to Motivate and Inspire Individual Contributors](https://www.dainemawer.com/motivate-individual-contributors.md): Published 2023-01-09. Five things that actually move the needle, beyond an annual review. ## Developer resources - [llms.txt](https://www.dainemawer.com/llms.txt): Structured index of every article, topic and feed. - [llms-full.txt](https://www.dainemawer.com/llms-full.txt): The index plus the full text of every article. - [agents.md](https://www.dainemawer.com/agents.md): This page, as Markdown. - [index.md](https://www.dainemawer.com/index.md): The homepage article index, as Markdown. - [sitemap.xml](https://www.dainemawer.com/sitemap.xml): Every canonical URL, with last-modified dates. - [rss.xml](https://www.dainemawer.com/rss.xml): RSS 2.0 feed of new articles. - [feed.json](https://www.dainemawer.com/feed.json): JSON Feed 1.1 equivalent of the RSS feed. - [robots.txt](https://www.dainemawer.com/robots.txt): Crawl rules, plus a Content-Signal header: search=yes, ai-input=yes, ai-train=no. ## Optional - [Full archive](https://www.dainemawer.com/index.md): Reverse-chronological index of every article, as Markdown. - [About](https://www.dainemawer.com/about.md): Work history, speaking, and how to verify who's writing this. ## Full article text ### Using CSS's New if() Function Without Breaking Safari and Firefox CSS's if() function picks a property's value based on a media, feature, or style condition, replacing separate @media, @supports, and container style query blocks with one inline declaration. It shipped in Chrome 137, but Firefox and Safari don't support it yet, so ship it as a second declaration after a plain fallback, not a replacement for one. CSS has had conditional logic for a while now: `@media` for viewport conditions, `@supports` for feature checks, `@container` for context based on an ancestor. Each one works, and each one asks you to write a separate block, usually with the same selector repeated, just to change one value. `if()` collapses that pattern into a single declaration. It shipped in Chrome 137 in 2026, and it's worth understanding now, both because it changes how you'll write conditional CSS once it's everywhere, and because it's genuinely usable today if you know where the edges are. ## What if() actually does The syntax is a list of condition-value pairs, separated by semicolons, evaluated in order, with an optional `else` at the end: ```css .button { padding: if(media(width > 700px): 1rem 1.5rem; else: 0.75rem 1rem); } ``` The first condition that evaluates to true wins. If nothing matches and there's no `else`, the property behaves as though it was never set, falling through to whatever the cascade would otherwise apply. That's the whole model. What makes it useful is what counts as a condition. ## Three condition types, one function `if()` accepts three kinds of tests, and you can mix them in the same declaration. **`media()`** checks a media feature, the same conditions `@media` accepts, just written inline: ```css .hero-title { font-size: if(media(width > 1024px): 3rem; else: 2rem); } ``` **`supports()`** checks feature support, the same thing `@supports` does, but scoped to one value instead of a whole rule: ```css .layout { display: if(supports(display: grid): grid; else: flex); } ``` **`style()`** checks the computed value of a custom property, which is the part that used to require a container style query and a separate block: ```css .card { padding: if(style(--density: compact): 0.75rem; else: 1.25rem); } ``` Set `--density: compact` anywhere up the cascade, on the element itself, a parent, a data attribute selector, and every property using that `style()` check picks it up. No `@container` wrapper, no naming a containment context, no duplicated selector. ## What it actually replaces What matters is the collapsing, not the syntax on its own. Here's the same responsive padding rule, before and after. Before, with a duplicated selector: ```css .button { padding: 0.75rem 1rem; } @media (width > 700px) { .button { padding: 1rem 1.5rem; } } ``` After, as one declaration: ```css .button { padding: if(media(width > 700px): 1rem 1.5rem; else: 0.75rem 1rem); } ``` Same result, no repeated selector, and the condition sits next to the value it controls instead of in a separate block somewhere else in the file. That's a small readability win on one property. On a component with five or six conditional values across breakpoints, states, and containers, it stops being small. You stop hunting through `@media` and `@container` blocks trying to reconstruct what a single element actually renders at a given width. ## Browser support today This is the part that matters before you touch it in production. As of September 2026, `if()` works in Chrome 137 and up, along with Edge and Opera, since all three share the Chromium engine. Firefox support is still in progress, and Safari has it listed on its roadmap for 2026-2027, not shipped. MDN currently marks `if()` as limited availability rather than Baseline, which means the usual rule applies: don't ship it as your only source of a value unless your audience is genuinely Chromium-only, an internal tool or an Electron app, for instance. The [State of CSS 2026](https://2026.stateofcss.com/en-US) developer survey found `if()` among the least-used features respondents reported, and browser support gaps around `if()` and the related `@function` mixins proposal came up repeatedly as the reason. That tracks. A feature that only works in one engine family isn't something most teams reach for on customer-facing work yet, whatever the syntax looks like. ## Shipping it safely today The good news is that CSS has handled this exact situation forever: a browser that doesn't recognize a value just discards that one declaration and moves on. You don't need a `@supports` wrapper around the whole rule to use `if()` safely. Write the plain value first, then the conditional version as a second declaration for the same property: ```css .card { padding: 1.25rem; padding: if(style(--density: compact): 0.75rem; else: 1.25rem); } ``` A browser without `if()` support parses the first line, fails to parse the second one because it doesn't recognize the function, and keeps the first value. A browser with support parses both and the second declaration, being later in the cascade, wins. No feature query needed, no branching stylesheet, just the same fallback-then-enhancement order you'd use for any newer CSS value. If you do want to gate a larger block of styles on `if()` support specifically, rather than fall back one property at a time, `@supports` still works for that: ```css @supports (padding: if(style(--density: compact): 1rem; else: 1rem)) { /* if()-dependent styles that need more than one property to make sense together */ } ``` Reach for that version when several related properties only make sense as a set. For a single value, the two-declaration pattern above is simpler and doesn't need a nested block at all. ## Where this is heading `if()` is part of a bigger CSS Working Group effort to bring conditional logic and reusable logic natively into stylesheets, alongside the related mixins and custom functions proposal. None of it replaces a preprocessor's full feature set yet, but it closes a real gap: today, expressing "this value depends on that condition" usually means either a duplicated `@media` block or reaching for JavaScript to toggle a class. `if()` makes that a one-line answer, once the browsers you actually need to support catch up. Until then, treat it the way you'd treat any Chromium-first feature: real value on Chromium-only surfaces now, a fallback-first pattern everywhere your audience includes Safari or Firefox users, which for most public sites is still most of your traffic. ## Further reading - [MDN: if() CSS function](https://developer.mozilla.org/en-US/docs/Web/CSS/if) - [Chrome for Developers: CSS conditionals with the new if() function](https://developer.chrome.com/blog/if-article) - [Can I Use: CSS if() function](https://caniuse.com/css-if) - [State of CSS 2026](https://2026.stateofcss.com/en-US) ### CSS Can Finally Replace Your Tooltip and Dropdown Libraries CSS anchor positioning (Chrome 125+, Safari 26+, Firefox 145 behind a flag) and appearance base-select (Chrome 135+, Safari 27+) now let native CSS handle floating tooltips and styled dropdowns without a JS library. Both degrade gracefully behind @supports, so you can adopt them now for new components and drop the library once support catches up. For years, two of the most common UI patterns on the web, a tooltip that stays attached to its trigger and a select dropdown you can actually style, have both needed a JavaScript library to do properly. Popper.js, Floating UI, and Tippy.js exist because CSS had no concept of "position this relative to that other element, and flip sides if it would overflow." Custom select components like react-select exist because the native `` element. Its trigger has always taken CSS fine. Its popup, the actual list of options, has never been reachable by a stylesheet, which is why teams have spent a decade reaching for `react-select`, `downshift`, or a hand-rolled combobox any time a design called for anything beyond the browser default. `appearance: base-select` changes that: ```css select, ::picker(select) { appearance: base-select; } ::picker(select) { border-radius: 8px; box-shadow: 0 8px 24px rgb(0 0 0 / 0.15); } select option:checked::before { content: '✓'; } ``` Setting `appearance: base-select` on both the ``, which matters more than it sounds: keyboard navigation, type-ahead, focus management, and the accessibility tree all keep working the way a native form control's are expected to, none of it needs reimplementing the way it does in a from-scratch listbox component. Per [Chrome for Developers' announcement post](https://developer.chrome.com/blog/a-customizable-select) and its [follow-up findings from developer feedback](https://developer.chrome.com/blog/rfc-customizable-select-findings), the feature is available starting in Chrome 135 and Safari 27. Firefox doesn't support it yet, but it fails safe: an unsupported browser just ignores the `appearance` value and renders the ordinary native select, unstyled but fully functional. That makes it one of the lowest-risk progressive enhancements available right now, closer in spirit to a CSS fallback than a feature you need a JS shim to polyfill. What matters more than the styling freedom is what you don't have to rebuild. A hand-rolled combobox has to reimplement arrow-key navigation, type-ahead-to-jump, `aria-activedescendant` wiring, and the focus behavior a screen reader expects from a listbox, and it's easy to get subtly wrong in ways that don't show up until an accessibility audit. A `