Archive: December 2025

  • Browser support at Clearleft

    Underlying our browser support policy are two foundational principles:

    1. Website content and core functionality should be accessible to everyone.
    2. It’s okay for websites to look different in different browsers.

    If content is unreadable in some browsers, that’s a bug that we will fix.

    If content is displayed slightly differently in some browsers, we consider that to be a facet of the web, not a bug. This means that there will sometimes be subtle visual and functional differences from browser to browser. We deem this acceptable provided content and core functionality are unaffected.

  • Note - Posted on

    Earlier this month, I noted down Jason’s advice on how to get hired as a developer. I’d also like to add the following point from @brandon:

    use the product

    this is one of the first questions i ask in the interview anyways, ship and share something you’ve built on top of cloudflare. this alone will set you apart from most of the candidates, and it will show you are genuinely interested.

    This complements Jason’s advice well, because it turns “show your work” into something concrete and product-specific.

  • Note - Posted on

    Today I learned that on macOS, with the default scrollbar setting enabled, classic scrollbars are shown automatically when a mouse is connected. I used to believe that, mouse or not, users needed to explicitly change the scrollbar appearance to get the classic scrollbars.

    This is relevant for everyone who builds websites because when classic scrollbars are shown, the value of 100vw includes the scrollbar width. This can cause an unexpected horizontal overflow if the layout relies on 100vw for full-width elements. Additionally, they affect media queries, which assume scrollbars don’t exist when evaluating viewport width.

    Having said that, these aren’t new issues. Classic scrollbars are shown by default on Windows, where the same behaviours apply.

    Further reading:

  • The f*** off contact page - Nic Chan

    Instead of seeing us as people who brought valuable knowledge and expertise to the project, they saw us as the hands that would execute their vision.

    The above bit from Nic captures a dynamic I want to address upfront with clients, so they never treat me as just a pair of hands.

    Then there’s the this bit, which really resonated with me (because I’ve been there too and it sucks):

    While I personally believe in the value of good design, I also believe there are a lot of smoke-and-mirrors in the industry, and I hated the thought that I might have inadvertently contributed to it. Even if the client is happy, it didn’t meet my internal bar for a quality product worth sticking my name on, and I feel like I’ve let down both the client and the end-users.

    Nic mentions how they hope to avoid similar situations in the future by blogging:

    By blogging, I’m putting a body of work out there that communicates my values and ethos. While much of the details of my client work has to remain private, these posts can be public, and hopefully they can help me find people who resonate with what I have to offer. Or you know, just be bold enough to communicate ‘Fuck off’ to those who don’t!

  • Selector scoping in element.querySelector() and element.querySelectorAll()

    I was reading the MDN docs for element.querySelector() and learnt something totally unexpected about selector scope when this method is called on an element.

    Let’s say I have the following HTML:

    <div>
    	<p>This is a paragraph and it has a <span>span inside</span> it.</p>
    </div>
    
    <script>
    	const baseElement = document.querySelector('p');
    	// Note: We are querying relative to the <p> element, not the document
    	const matchedSpan = baseElement.querySelector('div span');
    	console.log(matchedSpan);
    </script>

    Once the above JavaScript is executed, what do you expect to happen?

    I expected it to log null to the console. My reasoning was simple: I am calling the method on baseElement (the <p> tag), and that paragraph clearly does not contain a <div> inside it.

    But here’s what actually gets logged to the console:

    <span>span inside</span>

    This surprisingly returns the <span> from inside the <p>.

    Why does this happen? MDN explains it like this:

    […] selectors is first applied to the whole document, not the baseElement, to generate an initial list of potential elements. The resulting elements are then examined to see if they are descendants of baseElement.

    Note: element.querySelectorAll() also applies selector scoping in the same manner.

    If we want to restrict the selector to the element on which it is called then we should use the :scope pseudo-class.

    const scopedQuery = baseElement.querySelector(':scope div span');
    console.log(scopedQuery); // Returns null, as expected

    As mentioned in the MDN docs for :scope, this works because:

    When used within a DOM API call — such as querySelector(), querySelectorAll(), matches(), or Element.closest():scope matches the element on which the method was called.