<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://www.amitmerchant.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://www.amitmerchant.com/" rel="alternate" type="text/html" /><updated>2026-03-08T11:23:26+00:00</updated><id>https://www.amitmerchant.com/feed.xml</id><title type="html">Amit Merchant</title><subtitle>A blog on PHP, JavaScript, and more</subtitle><author><name>Amit Merchant</name></author><entry><title type="html">One CSS Property That Makes Numbers Look Instantly Better</title><link href="https://www.amitmerchant.com/one-css-property-that-makes-numbers-look-instantly-better/" rel="alternate" type="text/html" title="One CSS Property That Makes Numbers Look Instantly Better" /><published>2026-03-05T00:00:00+00:00</published><updated>2026-03-05T00:00:00+00:00</updated><id>https://www.amitmerchant.com/one-css-property-that-makes-numbers-look-instantly-better</id><content type="html" xml:base="https://www.amitmerchant.com/one-css-property-that-makes-numbers-look-instantly-better/"><![CDATA[<p>When you’re working with fonts, especially for displaying numbers, there’s something called <strong>proportional spacing</strong> that can make a huge difference in how your numbers look when they’re animated.</p>

<p>Essentially, most fonts (apart from <a href="https://en.wikipedia.org/wiki/Monospaced_font">monospaced fonts</a>) use <a href="https://www.webopedia.com/definitions/proportional-spacing/">proportional spacing</a>, which means that each character takes up a different amount of horizontal space. This can lead to numbers looking <strong>uneven</strong> and <strong>misaligned</strong>, especially when they’re displayed in a tabular format.</p>

<p>Here’s what it looks like.</p>

<p><img src="/images/proportional-pitch-vs-fixed-pitch.png" alt="proportional vs fixed pitch" /></p>

<p>This creates issues when numbers change, let’s say in clocks or counters, the text/numbers <em>shifts horizontally</em>, which can be visually jarring. In other words, it will cause a layout shift, which is not a good user experience.</p>

<blockquote>
  <p>To fix this issue, you can use the <code class="language-plaintext highlighter-rouge">font-variant-numeric</code> CSS property (which I <a href="https://x.com/sorenblank/status/2028520200417706017">recently stumbled upon</a>) with the value <code class="language-plaintext highlighter-rouge">tabular-nums</code>.</p>
</blockquote>

<p>This will force all the numbers in the font to take up the same amount of horizontal space, creating a more uniform and visually appealing look. Once this property is applied, the numbers behave like monospaced digits, even in proportional fonts.</p>

<p>I’ve created a <a href="https://codepen.io/amit_merchant/pen/dPpMNVd">CodePen demo</a> to show you how it works.</p>

<p class="codepen" data-height="300" data-pen-title="Tabular Numbers Demo" data-slug-hash="dPpMNVd" data-user="amit_merchant" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/amit_merchant/pen/dPpMNVd">
  Tabular Numbers Demo</a> by Amit Merchant (<a href="https://codepen.io/amit_merchant">@amit_merchant</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async="" src="https://public.codepenassets.com/embed/index.js"></script>

<p>Now, you might be wondering, what are all the places where this can be useful?</p>

<p>Well, here are a few examples:</p>

<ul>
  <li>Clocks and timers</li>
  <li>Financial tables</li>
  <li>Analytics dashboards</li>
  <li>Scoreboards</li>
  <li>Any place where numbers are displayed in a tabular format</li>
</ul>

<p>Also, this property is supported in all modern browsers, so you can safely use it in your projects without worrying about compatibility issues.</p>

<p>Read more about the <code class="language-plaintext highlighter-rouge">font-variant-numeric</code> property on <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/font-variant-numeric">MDN Web Docs</a>.</p>]]></content><author><name>Amit Merchant</name></author><category term="CSS" /><summary type="html"><![CDATA[When you’re working with fonts, especially for displaying numbers, there’s something called proportional spacing that can make a huge difference in how your numbers look when they’re animated.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.amitmerchant.com/cdn/one-css-property-that-makes-numbers-look-instantly-better.png" /><media:content medium="image" url="https://www.amitmerchant.com/cdn/one-css-property-that-makes-numbers-look-instantly-better.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Real-world examples of using Laravel AI SDK</title><link href="https://www.amitmerchant.com/real-world-exmples-using-laravel-api-sdk/" rel="alternate" type="text/html" title="Real-world examples of using Laravel AI SDK" /><published>2026-02-07T00:00:00+00:00</published><updated>2026-02-07T00:00:00+00:00</updated><id>https://www.amitmerchant.com/real-world-exmples-using-laravel-api-sdk</id><content type="html" xml:base="https://www.amitmerchant.com/real-world-exmples-using-laravel-api-sdk/"><![CDATA[<p>The Laravel team recently released the <a href="https://github.com/laravel/ai">Laravel AI SDK</a> that provides a simple and elegant way to integrate AI capabilities into your Laravel applications. The SDK offers a clean and intuitive API that allows developers to easily interact with AI models and services.</p>

<p>Essentially, the Laravel AI SDK abstracts away the complexities of working with AI models (such as <strong>OpenAI</strong>, <strong>Anthropic</strong>, <strong>Gemini</strong>, <strong>Mistral</strong>, <strong>Ollama</strong>, and more) and provides a seamless experience for developers to leverage AI in their applications. It supports various AI services, including natural language processing, image recognition, and more.</p>

<p>You can check <a href="https://laravel.com/docs/12.x/ai-sdk">this page</a> to learn how to get started with the Laravel AI SDK and explore its features. In this article, though, I want to share some real-world examples of how you can use the Laravel AI SDK in your applications.</p>

<ul id="markdown-toc">
  <li><a href="#mining-user-data-for-insights" id="markdown-toc-mining-user-data-for-insights">Mining user data for insights</a></li>
  <li><a href="#code-review-bot-for-prs-github--gitlab-integration" id="markdown-toc-code-review-bot-for-prs-github--gitlab-integration">Code Review Bot for PRs (GitHub / GitLab Integration)</a></li>
  <li><a href="#personalized-learning--quiz-system-for-an-edtech-app" id="markdown-toc-personalized-learning--quiz-system-for-an-edtech-app">Personalized Learning / Quiz System for an EdTech App</a></li>
  <li><a href="#in-closing" id="markdown-toc-in-closing">In closing</a></li>
</ul>

<h3 id="mining-user-data-for-insights">Mining user data for insights</h3>

<p>The simplest use case of the Laravel AI SDK is to mine user data for insights. For instance, you can use the SDK to analyze user models and related data to extract valuable insights.</p>

<p>For instance, you can use the <code class="language-plaintext highlighter-rouge">User</code> + <code class="language-plaintext highlighter-rouge">Order</code> Eloquent models as context for your AI queries.</p>

<p>Here’s an <code class="language-plaintext highlighter-rouge">AccountAssistant</code> assistant class that uses the Laravel AI SDK to analyze user data and provide insights:</p>

<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// app/Ai/Agents/AccountAssistant.php</span>

<span class="kn">namespace</span> <span class="nn">App\Ai\Agents</span><span class="p">;</span>

<span class="kn">use</span> <span class="nc">App\Models\User</span><span class="p">;</span>
<span class="kn">use</span> <span class="nc">App\Models\Order</span><span class="p">;</span>
<span class="kn">use</span> <span class="nc">Laravel\Ai\Contracts\Agent</span><span class="p">;</span>
<span class="kn">use</span> <span class="nc">Laravel\Ai\Contracts\Conversational</span><span class="p">;</span>
<span class="kn">use</span> <span class="nc">Laravel\Ai\Promptable</span><span class="p">;</span>
<span class="kn">use</span> <span class="nc">Stringable</span><span class="p">;</span>

<span class="kd">class</span> <span class="nc">AccountAssistant</span> <span class="kd">implements</span> <span class="nc">Agent</span><span class="p">,</span> <span class="nc">Conversational</span>
<span class="p">{</span>
    <span class="kn">use</span> <span class="nc">Promptable</span><span class="p">;</span>

    <span class="k">public</span> <span class="k">function</span> <span class="n">__construct</span><span class="p">(</span><span class="k">public</span> <span class="kt">User</span> <span class="nv">$user</span><span class="p">)</span> <span class="p">{}</span>

    <span class="k">public</span> <span class="k">function</span> <span class="n">instructions</span><span class="p">():</span> <span class="kt">Stringable</span><span class="o">|</span><span class="n">string</span>
    <span class="p">{</span>
        <span class="k">return</span> <span class="s1">'You are an assistant helping a customer with their account and recent orders.'</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">public</span> <span class="k">function</span> <span class="n">messages</span><span class="p">():</span> <span class="kt">iterable</span>
    <span class="p">{</span>
        <span class="c1">// You might combine past chat messages from a History model,</span>
        <span class="c1">// but you can also “prime” the conversation with DB data:</span>
        <span class="nv">$orders</span> <span class="o">=</span> <span class="nc">Order</span><span class="o">::</span><span class="nf">where</span><span class="p">(</span><span class="s1">'user_id'</span><span class="p">,</span> <span class="nv">$this</span><span class="o">-&gt;</span><span class="n">user</span><span class="o">-&gt;</span><span class="n">id</span><span class="p">)</span>
            <span class="o">-&gt;</span><span class="nf">latest</span><span class="p">()</span>
            <span class="o">-&gt;</span><span class="nf">limit</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span>
            <span class="o">-&gt;</span><span class="nf">get</span><span class="p">()</span>
            <span class="o">-&gt;</span><span class="nf">map</span><span class="p">(</span><span class="k">fn</span> <span class="p">(</span><span class="nv">$order</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="s2">"- #</span><span class="si">{</span><span class="nv">$order</span><span class="o">-&gt;</span><span class="n">id</span><span class="si">}</span><span class="s2"> (</span><span class="si">{</span><span class="nv">$order</span><span class="o">-&gt;</span><span class="n">status</span><span class="si">}</span><span class="s2">) total </span><span class="si">{</span><span class="nv">$order</span><span class="o">-&gt;</span><span class="n">total</span><span class="si">}</span><span class="s2">"</span><span class="p">);</span>

        <span class="k">return</span> <span class="p">[</span>
            <span class="c1">// System / context message built from Eloquent:</span>
            <span class="k">new</span> <span class="err">\</span><span class="nf">Laravel\Ai\Messages\Message</span><span class="p">(</span>
                <span class="s1">'system'</span><span class="p">,</span>
                <span class="s2">"Here are the user's 5 most recent orders:</span><span class="se">\n</span><span class="s2">"</span><span class="mf">.</span><span class="nv">$orders</span><span class="o">-&gt;</span><span class="nb">implode</span><span class="p">(</span><span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">)</span>
            <span class="p">),</span>
        <span class="p">];</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>And then use it like this:</p>

<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$assistant</span> <span class="o">=</span> <span class="err">\</span><span class="nc">App\Ai\Agents\AccountAssistant</span><span class="o">::</span><span class="nf">make</span><span class="p">(</span><span class="n">user</span><span class="o">:</span> <span class="nv">$user</span><span class="p">);</span>

<span class="nv">$response</span> <span class="o">=</span> <span class="nv">$assistant</span><span class="o">-&gt;</span><span class="nf">prompt</span><span class="p">(</span><span class="s1">'Where is my last order?'</span><span class="p">);</span>

<span class="k">return</span> <span class="p">(</span><span class="n">string</span><span class="p">)</span> <span class="nv">$response</span><span class="p">;</span>
</code></pre></div></div>

<p>In this example, the <code class="language-plaintext highlighter-rouge">AccountAssistant</code> class implements the <code class="language-plaintext highlighter-rouge">Agent</code> and <code class="language-plaintext highlighter-rouge">Conversational</code> interfaces provided by the Laravel AI SDK. The <code class="language-plaintext highlighter-rouge">instructions</code> method provides context for the AI model, while the <code class="language-plaintext highlighter-rouge">messages</code> method retrieves the user’s recent orders from the database and formats them as a system message.</p>

<p>When you call the <code class="language-plaintext highlighter-rouge">prompt</code> method with a user query (e.g., <strong>“Where is my last order?”</strong>), the AI model will analyze the provided context and generate a response based on the user’s recent orders.</p>

<h2 id="code-review-bot-for-prs-github--gitlab-integration">Code Review Bot for PRs (GitHub / GitLab Integration)</h2>

<p>You can use the SDK to build an internal <strong>“AI reviewer”</strong> that comments on pull requests in your Laravel monorepo.</p>

<p>At a high level:</p>

<ul>
  <li>
    <p>When a PR is opened or updated, your CI pipeline (or a small Laravel service) fetches the diff and key files.</p>
  </li>
  <li>
    <p>You create an agent like <code class="language-plaintext highlighter-rouge">CodeReviewer</code> whose <code class="language-plaintext highlighter-rouge">instructions()</code> explain your team’s standards: coding style, architecture rules, security expectations, performance guidelines, etc.</p>
  </li>
  <li>
    <p>In <code class="language-plaintext highlighter-rouge">prompt()</code>, you pass a structured prompt: a short summary of the PR, the diff, and maybe a list of changed files as attachments (e.g. <code class="language-plaintext highlighter-rouge">Files\Document::fromString($diff, 'text/plain')</code>).</p>
  </li>
  <li>
    <p>The agent uses structured output to return something like:</p>

    <ul>
      <li>
        <p><code class="language-plaintext highlighter-rouge">summary</code>: overall explanation of changes</p>
      </li>
      <li>
        <p><code class="language-plaintext highlighter-rouge">issues</code>: list of problems with severity and file/line hints</p>
      </li>
      <li>
        <p><code class="language-plaintext highlighter-rouge">suggestions</code>: refactors or tests to add</p>
      </li>
    </ul>
  </li>
  <li>
    <p>Your Laravel app then maps that output to actual comments on the PR via the Git hosting API.</p>
  </li>
</ul>

<p>This leverages agents, attachments, and structured output, but in a domain (code review automation) not explicitly shown in the docs.</p>

<h2 id="personalized-learning--quiz-system-for-an-edtech-app">Personalized Learning / Quiz System for an EdTech App</h2>

<p>Imagine you’re building an online course platform and want smarter, adaptive practice for students.</p>

<p>You could use the AI SDK to:</p>

<ul>
  <li>Generate personalized quizzes from course content</li>
</ul>

<p>When a student finishes a lesson, call an agent like <code class="language-plaintext highlighter-rouge">QuizGenerator</code> with:</p>

<ul>
  <li>
    <p><strong>Instructions:</strong> <em>“You are an exam coach. Generate questions only from the given lesson content, aligned to this student’s weaknesses.”</em></p>
  </li>
  <li>
    <p><strong>Messages:</strong> include a summary of what the student just studied, plus their past quiz results.</p>
  </li>
  <li>
    <p><strong>Attachments:</strong> lesson notes or slides as <code class="language-plaintext highlighter-rouge">Files\Document::fromStorage(...)</code>.The agent uses structured output to return a schema like:￼You then persist these questions in your <code class="language-plaintext highlighter-rouge">quizzes</code> and <code class="language-plaintext highlighter-rouge">quiz_questions</code> tables.</p>
  </li>
  <li>
    <p>Adapt difficulty using conversation context</p>
  </li>
</ul>

<p>Implement <code class="language-plaintext highlighter-rouge">Conversational</code> or <code class="language-plaintext highlighter-rouge">RemembersConversations</code> so the agent can see previous attempts, topics the student struggled with, and feedback given. The next quiz can intentionally focus on weak areas or gradually increase difficulty.</p>

<ul>
  <li>Explain answers on demand</li>
</ul>

<p>A second agent, <code class="language-plaintext highlighter-rouge">AnswerExplainer</code>, takes the student’s wrong answer and the original question as prompt, and returns a tailored explanation (“why B is wrong and C is right”), again via structured output so you can show explanations, hints, and follow‑up questions cleanly in your UI.</p>

<ul>
  <li>
    <p>Enhance with embeddings and reranking</p>
  </li>
  <li>
    <p>Use Embeddings + <code class="language-plaintext highlighter-rouge">whereVectorSimilarTo</code> to find the most relevant paragraphs in your course material for each question.</p>
  </li>
  <li>
    <p>Use Reranking to reorder candidate questions so the most relevant and appropriate ones are presented first.</p>
  </li>
</ul>

<p>This gives you an adaptive learning system built entirely on top of the Laravel AI SDK primitives: agents, structured output, files/attachments, conversation memory, embeddings, and reranking.</p>

<h2 id="in-closing">In closing</h2>

<p>These are just a few examples of how you can use the Laravel AI SDK in real-world applications. The SDK provides a lot of flexibility and power (such as <a href="https://laravel.com/docs/12.x/ai-sdk#remembering-conversations">remembering conversations</a> and <a href="https://laravel.com/docs/12.x/ai-sdk#middleware">middleware</a>), allowing you to build intelligent features that can enhance user experience and provide valuable insights.</p>

<p>Whether you’re mining user data, automating code reviews, or creating personalized learning experiences, the Laravel AI SDK has you covered.</p>]]></content><author><name>Amit Merchant</name></author><category term="Laravel" /><summary type="html"><![CDATA[The Laravel team recently released the Laravel AI SDK that provides a simple and elegant way to integrate AI capabilities into your Laravel applications. The SDK offers a clean and intuitive API that allows developers to easily interact with AI models and services.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.amitmerchant.com/cdn/real-world-examples-using-laravel-ai-sdk.png" /><media:content medium="image" url="https://www.amitmerchant.com/cdn/real-world-examples-using-laravel-ai-sdk.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Add UI sounds in your web apps in a heartbeat</title><link href="https://www.amitmerchant.com/add-ui-sounds-in-your-web-apps-in-hearbeat/" rel="alternate" type="text/html" title="Add UI sounds in your web apps in a heartbeat" /><published>2026-01-15T00:00:00+00:00</published><updated>2026-01-15T00:00:00+00:00</updated><id>https://www.amitmerchant.com/add-ui-sounds-in-your-web-apps-in-hearbeat</id><content type="html" xml:base="https://www.amitmerchant.com/add-ui-sounds-in-your-web-apps-in-hearbeat/"><![CDATA[<p>I was reading the article <a href="https://www.userinterface.wiki/sounds-on-the-web">Sounds on The Web</a> by <em>Raphael Salaja</em> the other day. The article explains the importance of sounds in web applications and how integrating them just enough can dramatically enhance user experience by providing feedback and improving usability.</p>

<p>It’s a really well-written piece that I highly recommend you to read. But I was wondering what they are using under the hood to add these sounds since the article itself doesn’t mention any libraries or tools. So, like always, I dug the source code, and I found there’s this magic library called <a href="https://snd.dev">SND</a> (also <a href="https://github.com/snd-lib/snd-lib">open-source</a>) is being used to add these sounds.</p>

<p>Now, it’s pretty interesting because SND is a tiny JavaScript library that makes it super easy to add UI sounds to your web applications. It provides a simple API to play sounds for various UI interactions like button clicks, notifications, warnings, errors, and more.</p>

<p>And as the article title suggests, it literally takes just a <em>“heartbeat”</em> to add sounds to your web apps with SND.</p>

<blockquote>
  <p>SND takes an interesting approach to use the sounds where once the library is included in your app, <strong>you just need to add predefined classes to the HTML elements</strong> where you want the sounds to be played.</p>
</blockquote>

<p>SND lets you choose from three audio kits, all of which have different sound signatures: <strong>sine</strong>, <strong>piano</strong>, and <strong>industrial</strong></p>

<p>So, for instance, you can include the SND library with the <em>sine kit</em> like this:</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;script </span><span class="na">src=</span><span class="s">"https://cdn.jsdelivr.net/gh/snd-lib/snd-lib@v1.2.4/dist/browser/snd.js?kit=01"</span><span class="nt">&gt;&lt;/script&gt;</span>
</code></pre></div></div>

<p>Here, the <code class="language-plaintext highlighter-rouge">kit=01</code> query parameter indicates that we want to use the sine kit. Similarly, you can use <code class="language-plaintext highlighter-rouge">kit=02</code> for the piano kit and <code class="language-plaintext highlighter-rouge">kit=03</code> for the industrial kit.</p>

<p>Next, if you want to play a click sound when a button is clicked, you just need to add the class <code class="language-plaintext highlighter-rouge">snd__button</code> to the button element like this:</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;button</span> <span class="na">class=</span><span class="s">"snd__button"</span><span class="nt">&gt;</span>Click Me<span class="nt">&lt;/button&gt;</span>
</code></pre></div></div>

<p>And the button starts playing a click sound on every click, just like that. No additional JavaScript code is needed!</p>

<p>Here’s a CodePen demo that I created to showcase how SND works.</p>

<p class="codepen" data-height="300" data-slug-hash="xbORXxO" data-pen-title="SND lib demo for UI sounds" data-user="amit_merchant" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
<span>See the Pen <a href="https://codepen.io/amit_merchant/pen/xbORXxO">
SND lib demo for UI sounds</a> by Amit Merchant (<a href="https://codepen.io/amit_merchant">@amit_merchant</a>)
on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async="" src="https://public.codepenassets.com/embed/index.js"></script>

<p>The good thing here is that even on repeated clicks, the sound plays without any delay or interruption. All the nitty-gritty of handling audio playback is taken care of by the library itself.</p>

<p>Also, if you want finer-grained control over the sounds, you can use the JavaScript API provided by SND. For example, you can play a sound programmatically like this:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">snd</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Snd</span><span class="p">();</span>

<span class="nx">snd</span><span class="p">.</span><span class="nx">load</span><span class="p">(</span><span class="nx">Snd</span><span class="p">.</span><span class="nx">KITS</span><span class="p">.</span><span class="nx">SND01</span><span class="p">).</span><span class="nx">then</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="c1">// you can add listeners to UI events:</span>
  <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="dl">"</span><span class="s2">btn</span><span class="dl">"</span><span class="p">).</span><span class="nx">addEventListener</span><span class="p">(</span><span class="dl">"</span><span class="s2">click</span><span class="dl">"</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="c1">// snd.play(Snd.SOUNDS.CELEBRATION);</span>
    <span class="nx">snd</span><span class="p">.</span><span class="nx">playNotification</span><span class="p">();</span>
  <span class="p">});</span>
<span class="p">});</span>
</code></pre></div></div>

<p>One more thing I noticed about SND is that it uses something called <strong>audio sprites</strong> (like <a href="https://en.wikipedia.org/wiki/Sprite_(computer_graphics)">image stripes</a>) to optimize the loading and playback of sounds. Instead of loading individual sound files for each UI interaction, SND combines multiple sounds into <a href="https://cdn.jsdelivr.net/gh/snd-lib/snd-lib@v1.2.4/assets/sounds/sprite/01/audioSprite.mp3">a single audio file</a> (sprite) and plays specific segments of that file as needed. This approach reduces the number of HTTP requests and improves performance.</p>

<p>Overall, I’m quite impressed with SND and would definitely consider using it in my future web projects that require UI sounds.</p>]]></content><author><name>Amit Merchant</name></author><category term="UI" /><summary type="html"><![CDATA[I was reading the article Sounds on The Web by Raphael Salaja the other day. The article explains the importance of sounds in web applications and how integrating them just enough can dramatically enhance user experience by providing feedback and improving usability.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.amitmerchant.com/cdn/add-ui-sounds-in-your-web-apps-in-hearbeat.png" /><media:content medium="image" url="https://www.amitmerchant.com/cdn/add-ui-sounds-in-your-web-apps-in-hearbeat.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">SVG Filters are just amazing!</title><link href="https://www.amitmerchant.com/svg-filters-are-just-amazing/" rel="alternate" type="text/html" title="SVG Filters are just amazing!" /><published>2026-01-12T00:00:00+00:00</published><updated>2026-01-12T00:00:00+00:00</updated><id>https://www.amitmerchant.com/svg-filters-are-just-amazing</id><content type="html" xml:base="https://www.amitmerchant.com/svg-filters-are-just-amazing/"><![CDATA[<p>So, lately, I have been exploring <a href="https://haleypark.design/">Haley Park’s website</a> and it’s pretty cute and well designed. What caught my attention, though, was this water ripple effect on one of the text snippets.</p>

<p>Here’s what it looks like.</p>

<div class="text-container">
    <p class="ripple-text">Breathe</p>
</div>
<svg style="position: absolute; width: 0px; height: 0px; pointer-events: none;">
    <filter id="water-ripple">
        <feTurbulence type="fractalNoise" baseFrequency="0.05" numOctaves="2" result="ripple">
        <animate attributeName="baseFrequency" dur="10s" values="0.02;0.05;0.02" repeatCount="indefinite"></animate>
        </feTurbulence>
        <feDisplacementMap in="SourceGraphic" in2="ripple" scale="5"></feDisplacementMap>
    </filter>
</svg>
<style>
.text-container {
    display: flex;
    justify-content: center;
    align-items: center;
}
.ripple-text {
    position: relative;
    filter: url(#water-ripple);
    display: inline-block;
    font-size: 3rem;
    font-weight: bold;
    color: #0e6cb6;
    font-family: "Iowan Old Style", "Palatino Linotype", "URW Palladio L", P052, serif;
}
</style>

<p>As you can see, the text features a beautiful water ripple effect. I dug through the source code and found out that it’s done using the <a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Guides/SVG_filters">SVG filters</a>!</p>

<p>So, I learned a bit about SVG filters, and here’s what I found out.</p>

<p>SVG filters are a powerful way to apply graphical effects to SVG elements. They can also be applied to HTML elements using the <code class="language-plaintext highlighter-rouge">filter</code> CSS property. Without SVG filters, achieving complex visual effects like the water ripple effect would require using images or complex JavaScript animations.</p>

<p>To recreate the water ripple effect, first, define the element you want to apply the effect to. In this case, it’s a simple paragraph with the text <strong>“Breathe”</strong>.</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;p</span> <span class="na">class=</span><span class="s">"ripple-text"</span><span class="nt">&gt;</span>
    Breathe
<span class="nt">&lt;/p&gt;</span>
</code></pre></div></div>

<p>Next, define the SVG filter that you want to apply.</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;svg</span> <span class="na">style=</span><span class="s">"position: absolute; width: 0px; height: 0px; pointer-events: none;"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;filter</span> <span class="na">id=</span><span class="s">"water-ripple"</span><span class="nt">&gt;</span>
        <span class="nt">&lt;feTurbulence</span> <span class="na">type=</span><span class="s">"fractalNoise"</span> <span class="na">baseFrequency=</span><span class="s">"0.05"</span> <span class="na">numOctaves=</span><span class="s">"2"</span> <span class="na">result=</span><span class="s">"ripple"</span><span class="nt">&gt;</span>
            <span class="nt">&lt;animate</span> <span class="na">attributeName=</span><span class="s">"baseFrequency"</span> <span class="na">dur=</span><span class="s">"10s"</span> <span class="na">values=</span><span class="s">"0.02;0.05;0.02"</span> <span class="na">repeatCount=</span><span class="s">"indefinite"</span><span class="nt">&gt;&lt;/animate&gt;</span>
        <span class="nt">&lt;/feTurbulence&gt;</span>
        <span class="nt">&lt;feDisplacementMap</span> <span class="na">in=</span><span class="s">"SourceGraphic"</span> <span class="na">in2=</span><span class="s">"ripple"</span> <span class="na">scale=</span><span class="s">"5"</span><span class="nt">&gt;&lt;/feDisplacementMap&gt;</span>
    <span class="nt">&lt;/filter&gt;</span>
<span class="nt">&lt;/svg&gt;</span>
</code></pre></div></div>

<p>As you can tell, we can define a filter using the <code class="language-plaintext highlighter-rouge">&lt;filter&gt;</code> element with id <code class="language-plaintext highlighter-rouge">water-ripple</code>.</p>

<p>Inside the filter, we use two filter primitives:</p>

<ul>
  <li><strong>The <code class="language-plaintext highlighter-rouge">&lt;feTurbulence&gt;</code> element</strong> - It’s used to create a noise texture, which is then animated to simulate the water ripple effect.</li>
  <li><strong>The <code class="language-plaintext highlighter-rouge">&lt;feDisplacementMap&gt;</code> element</strong> - It’s used to apply the turbulence effect to the text.</li>
</ul>

<p>There are a bunch of parameters on these filters that you can tweak to achieve different effects.</p>

<p>For instance, in the <code class="language-plaintext highlighter-rouge">&lt;feTurbulence&gt;</code> element, we set the <code class="language-plaintext highlighter-rouge">type</code> to <code class="language-plaintext highlighter-rouge">fractalNoise</code>, which creates a fractal noise pattern. The <code class="language-plaintext highlighter-rouge">baseFrequency</code> controls the frequency of the noise, and <code class="language-plaintext highlighter-rouge">numOctaves</code> controls the complexity of the noise.</p>

<p>Similarly, for the <code class="language-plaintext highlighter-rouge">&lt;feDisplacementMap&gt;</code> element, the <code class="language-plaintext highlighter-rouge">in</code> attribute specifies the input image (in this case, the original text), and the <code class="language-plaintext highlighter-rouge">in2</code> attribute specifies the turbulence effect created by <code class="language-plaintext highlighter-rouge">&lt;feTurbulence&gt;</code>. The <code class="language-plaintext highlighter-rouge">scale</code> attribute controls the amount of displacement applied to the text.</p>

<p>Also, the SVG is hidden from viewing by setting its width and height to 0 and positioning it absolutely.</p>

<blockquote>
  <p><strong>Fun fact:</strong> The <code class="language-plaintext highlighter-rouge">fe</code> in the filter primitive names stands for <em>“filter effect”</em>.</p>
</blockquote>

<p>Finally, apply the filter to the text by using the <code class="language-plaintext highlighter-rouge">id</code> of the filter in the CSS.</p>

<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">.ripple-text</span> <span class="p">{</span>
    <span class="nl">filter</span><span class="p">:</span> <span class="sx">url(#water-ripple)</span><span class="p">;</span>
    <span class="nl">font-size</span><span class="p">:</span> <span class="m">3rem</span><span class="p">;</span>
    <span class="nl">font-weight</span><span class="p">:</span> <span class="nb">bold</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>And that’s it! You now have a beautiful water ripple effect applied to your text using SVG filters.</p>

<p>I found this cool <a href="https://yoksel.github.io/svg-filters/">SVG filter playground</a> where you can experiment with different filter primitives and see the effects in real-time. You can stack multiple filter primitives, change their parameters, and create complex effects.</p>

<p>And if you want to learn more about SVG filters, check out the <a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Guides/SVG_filters">MDN documentation on SVG filters</a> for a comprehensive guide on how to use them.</p>]]></content><author><name>Amit Merchant</name></author><category term="CSS" /><summary type="html"><![CDATA[So, lately, I have been exploring Haley Park’s website and it’s pretty cute and well designed. What caught my attention, though, was this water ripple effect on one of the text snippets.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.amitmerchant.com/cdn/svg-filters-are-just-amazing.png" /><media:content medium="image" url="https://www.amitmerchant.com/cdn/svg-filters-are-just-amazing.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">The new clamp() function in PHP 8.6</title><link href="https://www.amitmerchant.com/the-clamp-function-in-php-86/" rel="alternate" type="text/html" title="The new clamp() function in PHP 8.6" /><published>2025-12-12T00:00:00+00:00</published><updated>2025-12-12T00:00:00+00:00</updated><id>https://www.amitmerchant.com/the-clamp-function-in-php-86</id><content type="html" xml:base="https://www.amitmerchant.com/the-clamp-function-in-php-86/"><![CDATA[<p>You know how sometimes you want to ensure that a value stays within a specific range? Maybe you’re working with user input, configuration values, or any scenario where you need to enforce boundaries.</p>

<p>In scenarios like these, having a built-in function to clamp values can be incredibly useful. Well, good news! PHP 8.6 <a href="https://wiki.php.net/rfc/clamp_v2">will be introducing</a> a new function called <code class="language-plaintext highlighter-rouge">clamp()</code> that does exactly that.</p>

<ul id="markdown-toc">
  <li><a href="#what-is-the-clamp-function" id="markdown-toc-what-is-the-clamp-function">What is the <code class="language-plaintext highlighter-rouge">clamp()</code> function?</a></li>
  <li><a href="#clamp-with-named-parameters" id="markdown-toc-clamp-with-named-parameters">Clamp with named parameters</a></li>
  <li><a href="#real-world-use-cases" id="markdown-toc-real-world-use-cases">Real-world use cases</a></li>
  <li><a href="#conclusion" id="markdown-toc-conclusion">Conclusion</a></li>
</ul>

<h2 id="what-is-the-clamp-function">What is the <code class="language-plaintext highlighter-rouge">clamp()</code> function?</h2>

<p>PHP 8.6’s <code class="language-plaintext highlighter-rouge">clamp()</code> function allows you to restrict a value to be within a specified minimum and maximum range. If the value is less than the minimum, it returns the minimum; if it’s greater than the maximum, it returns the maximum; otherwise, it returns the value itself.</p>

<p>Here’s the signature of the function:</p>

<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">clamp</span> <span class="p">(</span> <span class="n">mixed</span> <span class="nv">$value</span><span class="p">,</span> <span class="n">mixed</span> <span class="nv">$min</span><span class="p">,</span> <span class="n">mixed</span> <span class="nv">$max</span> <span class="p">)</span> <span class="o">:</span> <span class="n">mixed</span>
</code></pre></div></div>

<p>As you can tell, clamp takes three arguments, a <code class="language-plaintext highlighter-rouge">$value</code>, <code class="language-plaintext highlighter-rouge">$min</code>, and <code class="language-plaintext highlighter-rouge">$max</code>, then checks if $value is within the bounds of <code class="language-plaintext highlighter-rouge">$min</code> and <code class="language-plaintext highlighter-rouge">$max</code> (both inclusive).</p>

<ul>
  <li>If <code class="language-plaintext highlighter-rouge">$value</code> is less than <code class="language-plaintext highlighter-rouge">$min</code>, it returns <code class="language-plaintext highlighter-rouge">$min</code>.</li>
  <li>If <code class="language-plaintext highlighter-rouge">$value</code> is greater than <code class="language-plaintext highlighter-rouge">$max</code>, it returns <code class="language-plaintext highlighter-rouge">$max</code>.</li>
  <li>If <code class="language-plaintext highlighter-rouge">$value</code> is between <code class="language-plaintext highlighter-rouge">$min</code> and <code class="language-plaintext highlighter-rouge">$max</code>, it returns <code class="language-plaintext highlighter-rouge">$value</code>.</li>
</ul>

<p>It throws a <code class="language-plaintext highlighter-rouge">ValueError</code> if <strong>min</strong> &gt; <strong>max</strong> or if min/max are <strong>NAN</strong>.</p>

<p>Here’s a simple example of how it works:</p>

<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$value1</span> <span class="o">=</span> <span class="nf">clamp</span><span class="p">(</span><span class="mi">15</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="mi">20</span><span class="p">);</span> <span class="c1">// Returns 15</span>
<span class="nv">$value2</span> <span class="o">=</span> <span class="nf">clamp</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="mi">20</span><span class="p">);</span>  <span class="c1">// Returns 10</span>
<span class="nv">$value3</span> <span class="o">=</span> <span class="nf">clamp</span><span class="p">(</span><span class="mi">25</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="mi">20</span><span class="p">);</span> <span class="c1">// Returns 20</span>
</code></pre></div></div>

<blockquote>
  <p><strong>Fun fact:</strong> A long time ago, I wrote a <a href="/a-simple-clamp-function-in-php/">custom clamp function</a> in PHP as a utility function for my projects.</p>
</blockquote>

<h2 id="clamp-with-named-parameters">Clamp with named parameters</h2>

<p>The clamp function becomes even more straight-forward with <a href="/proposed-named-arguments-php/">named parameters</a> and re-ordering of the arguments.</p>

<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$brightness</span> <span class="o">=</span> <span class="nf">clamp</span><span class="p">(</span><span class="n">min</span><span class="o">:</span> <span class="mi">0</span><span class="p">,</span> <span class="n">value</span><span class="o">:</span> <span class="nv">$brightness</span><span class="p">,</span> <span class="n">max</span><span class="o">:</span> <span class="mi">100</span><span class="p">);</span>
</code></pre></div></div>

<h2 id="real-world-use-cases">Real-world use cases</h2>

<p>Here are some practical scenarios where the <code class="language-plaintext highlighter-rouge">clamp()</code> function can come in handy.</p>

<p><strong>User input:</strong> keep a percentage between 0 and 100</p>

<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$percentage</span> <span class="o">=</span> <span class="nf">clamp</span><span class="p">(</span><span class="nv">$percentage</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">100</span><span class="p">);</span>
</code></pre></div></div>

<p><strong>UI sliders:</strong> constrain a volume setting between 0 and 10</p>

<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$volume</span> <span class="o">=</span> <span class="nf">clamp</span><span class="p">(</span><span class="nv">$volume</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">10</span><span class="p">);</span>
</code></pre></div></div>

<p><strong>Pagination:</strong> bound page number between the first and last page</p>

<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$page</span> <span class="o">=</span> <span class="nf">clamp</span><span class="p">((</span><span class="n">int</span><span class="p">)</span><span class="nv">$_GET</span><span class="p">[</span><span class="s1">'page'</span><span class="p">]</span> <span class="o">??</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="nv">$totalPages</span><span class="p">);</span>
</code></pre></div></div>

<p><strong>Rate limiting:</strong> prevent burst count from exceeding a ceiling</p>

<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$requests</span> <span class="o">=</span> <span class="nf">clamp</span><span class="p">(</span><span class="nv">$requests</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="nv">$maxBurst</span><span class="p">);</span>
</code></pre></div></div>

<p><strong>Dates:</strong> ensure a booking date falls within an allowed window</p>

<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$date</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">DateTimeImmutable</span><span class="p">(</span><span class="nv">$input</span><span class="p">);</span>
<span class="nv">$start</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">DateTimeImmutable</span><span class="p">(</span><span class="s1">'2025-08-15'</span><span class="p">);</span>
<span class="nv">$end</span>   <span class="o">=</span> <span class="k">new</span> <span class="nc">DateTimeImmutable</span><span class="p">(</span><span class="s1">'2025-09-15'</span><span class="p">);</span>
<span class="nv">$clamped</span> <span class="o">=</span> <span class="nf">clamp</span><span class="p">(</span><span class="nv">$date</span><span class="p">,</span> <span class="nv">$start</span><span class="p">,</span> <span class="nv">$end</span><span class="p">);</span> <span class="c1">// returns start/end/date accordingly</span>
</code></pre></div></div>

<p><strong>Geometry:</strong> restrict an angle between 0 and 90</p>

<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$angle</span> <span class="o">=</span> <span class="nf">clamp</span><span class="p">(</span><span class="nv">$angle</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">90</span><span class="p">);</span>
</code></pre></div></div>

<p><strong>Strings (lexicographic):</strong> keep a tag between <em>“c”</em> and <em>“g”</em></p>

<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$tag</span> <span class="o">=</span> <span class="nf">clamp</span><span class="p">(</span><span class="nv">$tag</span><span class="p">,</span> <span class="s2">"c"</span><span class="p">,</span> <span class="s2">"g"</span><span class="p">);</span>
</code></pre></div></div>

<h2 id="conclusion">Conclusion</h2>

<p>The <code class="language-plaintext highlighter-rouge">clamp()</code> function in PHP 8.6 is a simple yet powerful addition that can help you manage values effectively by enforcing boundaries. Whether you’re dealing with user input, configuration settings, or any scenario where you need to ensure values stay within a specific range, <code class="language-plaintext highlighter-rouge">clamp()</code> provides a clean and efficient solution.</p>

<p>Read more about the <code class="language-plaintext highlighter-rouge">clamp()</code> function in <a href="https://wiki.php.net/rfc/clamp_v2">this RFC</a>.</p>]]></content><author><name>Amit Merchant</name></author><category term="PHP" /><summary type="html"><![CDATA[You know how sometimes you want to ensure that a value stays within a specific range? Maybe you’re working with user input, configuration values, or any scenario where you need to enforce boundaries.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.amitmerchant.com/cdn/the-clamp-function-php-86.png" /><media:content medium="image" url="https://www.amitmerchant.com/cdn/the-clamp-function-php-86.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Partial Function Application is coming in PHP 8.6</title><link href="https://www.amitmerchant.com/partial-function-application-php-86/" rel="alternate" type="text/html" title="Partial Function Application is coming in PHP 8.6" /><published>2025-12-05T00:00:00+00:00</published><updated>2025-12-05T00:00:00+00:00</updated><id>https://www.amitmerchant.com/partial-function-application-php-86</id><content type="html" xml:base="https://www.amitmerchant.com/partial-function-application-php-86/"><![CDATA[<p>Ever reach for a simple callback and end up writing a tiny novella—an arrow function stuffed with types, reordered parameters, and boilerplate just to pass one value through?</p>

<p>Well, it looks like PHP 8.6 is set to make our lives easier with the introduction of <a href="https://wiki.php.net/rfc/partial_function_application_v2">Partial Function Application</a>.</p>

<ul id="markdown-toc">
  <li><a href="#what-is-partial-function-application" id="markdown-toc-what-is-partial-function-application">What is Partial Function Application?</a></li>
  <li><a href="#a-real-world-example" id="markdown-toc-a-real-world-example">A real-world example</a></li>
  <li><a href="#common-pfa-patterns" id="markdown-toc-common-pfa-patterns">Common PFA patterns</a></li>
  <li><a href="#in-closing" id="markdown-toc-in-closing">In closing</a></li>
</ul>

<h2 id="what-is-partial-function-application">What is Partial Function Application?</h2>

<p>Partial Function Application in PHP 8.6 will let you write a <strong>“pre‑configured”</strong> callable by calling a function with some arguments and using placeholders for the rest. Instead of executing, PHP returns a Closure whose parameter list is auto‑derived from the missing parts.</p>

<p>Placeholders are:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">?</code> for <strong>“exactly one argument here”</strong></li>
  <li><code class="language-plaintext highlighter-rouge">…</code> for <strong>“forward any remaining arguments”</strong></li>
</ul>

<p>Here’s a <strong>basic example</strong> of how it works.</p>

<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">function</span> <span class="n">add4</span><span class="p">(</span><span class="kt">int</span> <span class="nv">$a</span><span class="p">,</span> <span class="kt">int</span> <span class="nv">$b</span><span class="p">,</span> <span class="kt">int</span> <span class="nv">$c</span><span class="p">,</span> <span class="kt">int</span> <span class="nv">$d</span><span class="p">):</span> <span class="kt">int</span> 
<span class="p">{</span>
    <span class="k">return</span> <span class="nv">$a</span> <span class="o">+</span> <span class="nv">$b</span> <span class="o">+</span> <span class="nv">$c</span> <span class="o">+</span> <span class="nv">$d</span><span class="p">;</span>
<span class="p">}</span>

<span class="c1">// Fill some now, leave one for later:</span>
<span class="nv">$f</span> <span class="o">=</span> <span class="nf">add4</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="o">?</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">);</span>
<span class="c1">// Equivalent to:</span>
<span class="nv">$f</span> <span class="o">=</span> <span class="k">static</span> <span class="k">fn</span><span class="p">(</span><span class="kt">int</span> <span class="nv">$b</span><span class="p">):</span> <span class="kt">int</span> <span class="o">=&gt;</span> <span class="nf">add4</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="nv">$b</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">);</span>

<span class="k">echo</span> <span class="nv">$f</span><span class="p">(</span><span class="mi">2</span><span class="p">);</span> <span class="c1">// 1+2+3+4 = 10</span>
</code></pre></div></div>

<p>As you can see in the example above, we created a new callable <code class="language-plaintext highlighter-rouge">$f</code> by partially applying the <code class="language-plaintext highlighter-rouge">add4</code> function with some arguments and using a placeholder for the missing argument. We can then call <code class="language-plaintext highlighter-rouge">$f</code> with the remaining argument to get the final result.</p>

<p>You can also call PFA an extension of <a href="/first-class-callables-in-php-8-1/">first-class callables</a>.</p>

<p><strong>You can leave multiple holes as well.</strong></p>

<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$f</span> <span class="o">=</span> <span class="nf">add4</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="o">?</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="o">?</span><span class="p">);</span>
<span class="c1">// Equivalent:</span>
<span class="nv">$f</span> <span class="o">=</span> <span class="k">static</span> <span class="k">fn</span><span class="p">(</span><span class="kt">int</span> <span class="nv">$b</span><span class="p">,</span> <span class="kt">int</span> <span class="nv">$d</span><span class="p">):</span> <span class="kt">int</span> <span class="o">=&gt;</span> <span class="nf">add4</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="nv">$b</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="nv">$d</span><span class="p">);</span>

<span class="k">echo</span> <span class="nv">$f</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="mi">7</span><span class="p">);</span> <span class="c1">// 1+5+3+7 = 16</span>
</code></pre></div></div>

<p><strong>And “Everything else” with <code class="language-plaintext highlighter-rouge">…</code></strong></p>

<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$f</span> <span class="o">=</span> <span class="nf">add4</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mf">...</span><span class="p">);</span>
<span class="c1">// Equivalent:</span>
<span class="nv">$f</span> <span class="o">=</span> <span class="k">static</span> <span class="k">fn</span><span class="p">(</span><span class="kt">int</span> <span class="nv">$b</span><span class="p">,</span> <span class="kt">int</span> <span class="nv">$c</span><span class="p">,</span> <span class="kt">int</span> <span class="nv">$d</span><span class="p">):</span> <span class="kt">int</span> <span class="o">=&gt;</span> <span class="nf">add4</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="nv">$b</span><span class="p">,</span> <span class="nv">$c</span><span class="p">,</span> <span class="nv">$d</span><span class="p">);</span>

<span class="k">echo</span> <span class="nv">$f</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">);</span> <span class="c1">// 10</span>
</code></pre></div></div>

<p>With PFA, <strong>callbacks become concise and intention-revealing.</strong> No more boilerplate arrow functions just to rearrange or fix arguments. Just plug in <code class="language-plaintext highlighter-rouge">?</code> and <code class="language-plaintext highlighter-rouge">…</code> where needed, and PHP does the rest.</p>

<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$strings</span> <span class="o">=</span> <span class="p">[</span><span class="s1">'hello world'</span><span class="p">,</span> <span class="s1">'hello there'</span><span class="p">];</span>

<span class="c1">// Without PFA (verbose):</span>
<span class="nv">$result</span> <span class="o">=</span> <span class="nb">array_map</span><span class="p">(</span><span class="k">static</span> <span class="k">fn</span><span class="p">(</span><span class="kt">string</span> <span class="nv">$s</span><span class="p">):</span> <span class="kt">string</span> <span class="o">=&gt;</span> <span class="nb">str_replace</span><span class="p">(</span><span class="s1">'hello'</span><span class="p">,</span> <span class="s1">'hi'</span><span class="p">,</span> <span class="nv">$s</span><span class="p">),</span> <span class="nv">$strings</span><span class="p">);</span>

<span class="c1">// With PFA:</span>
<span class="nv">$result</span> <span class="o">=</span> <span class="nb">array_map</span><span class="p">(</span><span class="nb">str_replace</span><span class="p">(</span><span class="s1">'hello'</span><span class="p">,</span> <span class="s1">'hi'</span><span class="p">,</span> <span class="o">?</span><span class="p">),</span> <span class="nv">$strings</span><span class="p">);</span>
<span class="c1">// Each element is fed into the ? at the $subject position.</span>
</code></pre></div></div>

<p><strong>It’s also pipe operator-friendly.</strong></p>

<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$foo</span>
  <span class="o">|&gt;</span> <span class="nb">array_map</span><span class="p">(</span><span class="nb">strtoupper</span><span class="p">(</span><span class="mf">...</span><span class="p">),</span> <span class="o">?</span><span class="p">)</span>
  <span class="o">|&gt;</span> <span class="nb">array_filter</span><span class="p">(</span><span class="o">?</span><span class="p">,</span> <span class="nb">is_numeric</span><span class="p">(</span><span class="mf">...</span><span class="p">));</span>
<span class="c1">// Right side of the pipe needs a unary callable; PFA supplies it concisely.</span>
</code></pre></div></div>

<p><strong>Named arguments and order.</strong></p>

<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">function</span> <span class="n">stuff</span><span class="p">(</span><span class="kt">int</span> <span class="nv">$i</span><span class="p">,</span> <span class="kt">string</span> <span class="nv">$s</span><span class="p">,</span> <span class="kt">float</span> <span class="nv">$f</span><span class="p">,</span> <span class="kt">Point</span> <span class="nv">$p</span><span class="p">,</span> <span class="kt">int</span> <span class="nv">$m</span> <span class="o">=</span> <span class="mi">0</span><span class="p">):</span> <span class="kt">string</span> <span class="p">{</span> <span class="cm">/* ... */</span> <span class="p">}</span>

<span class="c1">// Named values out of order still work:</span>
<span class="nv">$c</span> <span class="o">=</span> <span class="nf">stuff</span><span class="p">(</span><span class="o">?</span><span class="p">,</span> <span class="o">?</span><span class="p">,</span> <span class="n">f</span><span class="o">:</span> <span class="mf">3.5</span><span class="p">,</span> <span class="n">p</span><span class="o">:</span> <span class="nv">$point</span><span class="p">);</span>
<span class="c1">// Closure expects (int $i, string $s)</span>

<span class="c1">// Named placeholders define their own parameter order:</span>
<span class="nv">$c</span> <span class="o">=</span> <span class="nf">stuff</span><span class="p">(</span><span class="n">s</span><span class="o">:</span> <span class="o">?</span><span class="p">,</span> <span class="n">i</span><span class="o">:</span> <span class="o">?</span><span class="p">,</span> <span class="n">p</span><span class="o">:</span> <span class="o">?</span><span class="p">,</span> <span class="n">f</span><span class="o">:</span> <span class="mf">3.5</span><span class="p">);</span>
<span class="c1">// Closure expects (string $s, int $i, Point $p)</span>
</code></pre></div></div>

<p><strong>Variadic functions.</strong></p>

<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">function</span> <span class="n">things</span><span class="p">(</span><span class="kt">int</span> <span class="nv">$i</span><span class="p">,</span> <span class="kt">?float</span> <span class="nv">$f</span> <span class="o">=</span> <span class="kc">null</span><span class="p">,</span> <span class="kt">Point</span> <span class="mf">...</span><span class="nv">$points</span><span class="p">)</span> <span class="p">{</span> <span class="cm">/* ... */</span> <span class="p">}</span>

<span class="c1">// Keep variadic open:</span>
<span class="nv">$c</span> <span class="o">=</span> <span class="nf">things</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mf">3.14</span><span class="p">,</span> <span class="mf">...</span><span class="p">);</span>
<span class="c1">// Closure expects (Point ...$points)</span>

<span class="c1">// Force exact count (variadic becomes required slots):</span>
<span class="nv">$c</span> <span class="o">=</span> <span class="nf">things</span><span class="p">(</span><span class="o">?</span><span class="p">,</span> <span class="o">?</span><span class="p">,</span> <span class="o">?</span><span class="p">,</span> <span class="o">?</span><span class="p">);</span>
<span class="c1">// Closure expects (int $i, ?float $f, Point $points0, Point $points1)</span>
</code></pre></div></div>

<p>You can also implement <a href="https://en.wikipedia.org/wiki/Thunk">Thunk functions</a> easily with PFAs.</p>

<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">function</span> <span class="n">expensive</span><span class="p">(</span><span class="kt">int</span> <span class="nv">$a</span><span class="p">,</span> <span class="kt">int</span> <span class="nv">$b</span><span class="p">,</span> <span class="kt">Point</span> <span class="nv">$c</span><span class="p">)</span> <span class="p">{</span> <span class="cm">/* heavy work */</span> <span class="p">}</span>

<span class="c1">// Prefill all, delay execution:</span>
<span class="nv">$thunk</span> <span class="o">=</span> <span class="nf">expensive</span><span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="nv">$pt</span><span class="p">,</span> <span class="mf">...</span><span class="p">);</span> <span class="c1">// Closure with zero required params</span>

<span class="c1">// Later:</span>
<span class="nv">$result</span> <span class="o">=</span> <span class="nv">$thunk</span><span class="p">();</span>
</code></pre></div></div>

<p><strong>You cannot partially apply constructors (new).</strong> Instead, you can use static methods or factory functions.</p>

<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$maker</span> <span class="o">=</span> <span class="nc">Widget</span><span class="o">::</span><span class="nf">make</span><span class="p">(</span><span class="o">?</span><span class="p">,</span> <span class="n">size</span><span class="o">:</span> <span class="mi">10</span><span class="p">);</span> <span class="c1">// OK</span>
<span class="nv">$new</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Widget</span><span class="p">(</span><span class="o">?</span><span class="p">,</span> <span class="mi">10</span><span class="p">);</span>           <span class="c1">// Compile error</span>
</code></pre></div></div>

<h2 id="a-real-world-example">A real-world example</h2>

<p>Here’s a more practical example of using PFA where we want to add headers to an HTTP request. We can <strong>prefill the header name and value</strong>, leaving the request array to be provided later.</p>

<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">function</span> <span class="n">addHeader</span><span class="p">(</span><span class="kt">array</span> <span class="nv">$req</span><span class="p">,</span> <span class="kt">string</span> <span class="nv">$name</span><span class="p">,</span> <span class="kt">string</span> <span class="nv">$value</span><span class="p">):</span> <span class="kt">array</span> 
<span class="p">{</span>
  <span class="nv">$req</span><span class="p">[</span><span class="s1">'headers'</span><span class="p">][</span><span class="nv">$name</span><span class="p">]</span> <span class="o">=</span> <span class="nv">$value</span><span class="p">;</span> 
  
  <span class="k">return</span> <span class="nv">$req</span><span class="p">;</span>
<span class="p">}</span>

<span class="c1">// Hole for the request; prefill header name/value</span>
<span class="nv">$withAuth</span> <span class="o">=</span> <span class="nf">addHeader</span><span class="p">(</span><span class="o">?</span><span class="p">,</span> <span class="s1">'Authorization'</span><span class="p">,</span> <span class="s1">'Bearer TOKEN'</span><span class="p">);</span>

<span class="nv">$req</span> <span class="o">=</span> <span class="p">[</span><span class="s1">'url'</span> <span class="o">=&gt;</span> <span class="s1">'/me'</span><span class="p">,</span> <span class="s1">'headers'</span> <span class="o">=&gt;</span> <span class="p">[]];</span>
<span class="nv">$req</span> <span class="o">=</span> <span class="nv">$withAuth</span><span class="p">(</span><span class="nv">$req</span><span class="p">);</span>
</code></pre></div></div>

<p>This way, we create a reusable callable <code class="language-plaintext highlighter-rouge">$withAuth</code> that adds the Authorization header to any request array we provide later.</p>

<h2 id="common-pfa-patterns">Common PFA patterns</h2>

<p>Here are quick patterns that can be associated with PFAs.</p>

<ul>
  <li><strong>Unary callback:</strong> <code class="language-plaintext highlighter-rouge">array_map(in_array(?, $allowed, strict: true), $input)</code></li>
  <li><strong>Fill “from the left,” leave rest:</strong> <code class="language-plaintext highlighter-rouge">stuff(1, 'two', …)</code></li>
  <li><strong>Named setup, leave rest:</strong> <code class="language-plaintext highlighter-rouge">stuff(f: 3.14, s: 'two', …)</code></li>
  <li><strong>First‑class callable (degenerate case):</strong> <code class="language-plaintext highlighter-rouge">func(…)</code></li>
</ul>

<h2 id="in-closing">In closing</h2>

<p>Partial Function Application will be a powerful addition to PHP 8.6 that can significantly reduce boilerplate and improve code clarity when working with callbacks. By allowing you to pre-configure functions with placeholders, PFA makes it easy to create concise, intention-revealing callables without the need for verbose arrow functions.</p>

<p>Read more about Partial Function Application in the <a href="https://wiki.php.net/rfc/partial_function_application_v2">official RFC</a>.</p>]]></content><author><name>Amit Merchant</name></author><category term="PHP" /><summary type="html"><![CDATA[Ever reach for a simple callback and end up writing a tiny novella—an arrow function stuffed with types, reordered parameters, and boilerplate just to pass one value through?]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.amitmerchant.com/cdn/partial-function-application-php-86.png" /><media:content medium="image" url="https://www.amitmerchant.com/cdn/partial-function-application-php-86.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">One CSS Trick to Eliminate Scrollbar Layout Shifts</title><link href="https://www.amitmerchant.com/one-css-trick-to-eliminate-scrollbar-layout-shifts/" rel="alternate" type="text/html" title="One CSS Trick to Eliminate Scrollbar Layout Shifts" /><published>2025-11-28T00:00:00+00:00</published><updated>2025-11-28T00:00:00+00:00</updated><id>https://www.amitmerchant.com/one-css-trick-to-eliminate-scrollbar-layout-shifts</id><content type="html" xml:base="https://www.amitmerchant.com/one-css-trick-to-eliminate-scrollbar-layout-shifts/"><![CDATA[<p>When a webpage’s content exceeds the viewport height, browsers typically introduce a vertical scrollbar. This can lead to layout shifts, especially when navigating between pages or toggling content visibility, as the presence or absence of the scrollbar alters the available width for content.</p>

<p>For instance, I faced this issue in <a href="https://notepad.js.org">one of my apps</a> where once the content overflows, the scrollbar appears, causing the text in the textarea to shift leftwards.</p>

<p><img src="/images/without-scrollbar-gutter.gif" alt="Without Scrollbar Gutter" /></p>

<p>To address this issue, you can use the <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/scrollbar-gutter">scrollbar-gutter</a> CSS property, which allows you to reserve space for the scrollbar, preventing layout shifts when it appears or disappears.</p>

<p>More specifically, you can set the <code class="language-plaintext highlighter-rouge">scrollbar-gutter</code> property to <code class="language-plaintext highlighter-rouge">stable</code> on the <code class="language-plaintext highlighter-rouge">body</code> element (or any other container element) to ensure that space for the scrollbar is always reserved.</p>

<p>In my case, I added this to the textarea element itself.</p>

<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">textarea</span> <span class="p">{</span>
  <span class="py">scrollbar-gutter</span><span class="p">:</span> <span class="n">stable</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This way, the layout remains consistent regardless of whether the scrollbar is present or not. This can be seen in the following GIF, where the content of the textarea doesn’t shift when the scrollbar appears.</p>

<p><img src="/images/with-scrollbar-gutter.gif" alt="With Scrollbar Gutter" /></p>

<p>The <code class="language-plaintext highlighter-rouge">scrollbar-gutter</code> property is a fairly new addition to CSS and is supported (since <strong>December 2024</strong>) in most modern browsers. So, you can confidently use it to enhance the user experience on your web pages.</p>]]></content><author><name>Amit Merchant</name></author><category term="CSS" /><summary type="html"><![CDATA[When a webpage’s content exceeds the viewport height, browsers typically introduce a vertical scrollbar. This can lead to layout shifts, especially when navigating between pages or toggling content visibility, as the presence or absence of the scrollbar alters the available width for content.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.amitmerchant.com/cdn/one-css-trick-to-eliminate-scrollbar-layout-shifts.png" /><media:content medium="image" url="https://www.amitmerchant.com/cdn/one-css-trick-to-eliminate-scrollbar-layout-shifts.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">The modern way to draw squircles using corner-shape in CSS</title><link href="https://www.amitmerchant.com/the-modern-way-to-draw-squircles-using-corner-shape-in-css/" rel="alternate" type="text/html" title="The modern way to draw squircles using corner-shape in CSS" /><published>2025-11-13T00:00:00+00:00</published><updated>2025-11-13T00:00:00+00:00</updated><id>https://www.amitmerchant.com/the-modern-way-to-draw-squircles-using-corner-shape-in-css</id><content type="html" xml:base="https://www.amitmerchant.com/the-modern-way-to-draw-squircles-using-corner-shape-in-css/"><![CDATA[<p>For years, we’ve faked <strong>“characterful”</strong> corners with SVG masks, pseudo-elements, and more than a few headaches. The problem? Border-radius only controls size, not shape.</p>

<p>The experimental (and fairly new) <code class="language-plaintext highlighter-rouge">corner-shape</code> property changes all that by letting us define the actual shape of an element’s corners directly in CSS. <em>Squircles! I’m looking at you</em>.</p>

<p>With a single declaration, we can turn convex curves into <strong>concave scoops</strong>, <strong>carve precise notches</strong>, or dial in <strong>superellipse geometry</strong>, while borders, shadows, and backgrounds follow the cut. It’s progressive enhancement in the truest sense: a new layer of polish, no hacks required.</p>

<ul id="markdown-toc">
  <li><a href="#what-is-the-corner-shape-property" id="markdown-toc-what-is-the-corner-shape-property">What is the <code class="language-plaintext highlighter-rouge">corner-shape</code> property?</a></li>
  <li><a href="#how-it-differs-from-border-radius" id="markdown-toc-how-it-differs-from-border-radius">How it differs from border-radius</a></li>
  <li><a href="#some-examples" id="markdown-toc-some-examples">Some examples</a></li>
  <li><a href="#what-about-fallbacks" id="markdown-toc-what-about-fallbacks">What about fallbacks?</a></li>
  <li><a href="#best-practices" id="markdown-toc-best-practices">Best practices</a></li>
</ul>

<h2 id="what-is-the-corner-shape-property">What is the <code class="language-plaintext highlighter-rouge">corner-shape</code> property?</h2>

<p>The <code class="language-plaintext highlighter-rouge">corner-shape</code> is an experimental CSS shorthand that defines the actual shape of a box’s corners within the area established by <code class="language-plaintext highlighter-rouge">border-radius</code>.</p>

<p>It can use keywords like <strong>round</strong>, <strong>scoop</strong>, <strong>bevel</strong>, <strong>notch</strong>, <strong>square</strong>, <strong>squircle</strong>, or a numeric <code class="language-plaintext highlighter-rouge">superellipse()</code> function. Backgrounds, borders, outlines, shadows, overflow, and backdrop-filter will follow the defined corner shape.</p>

<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">.box</span> <span class="p">{</span>
  <span class="nl">width</span><span class="p">:</span> <span class="m">200px</span><span class="p">;</span>
  <span class="nl">height</span><span class="p">:</span> <span class="m">200px</span><span class="p">;</span>
  <span class="nl">border-radius</span><span class="p">:</span> <span class="m">32px</span><span class="p">;</span>
  <span class="py">corner-shape</span><span class="p">:</span> <span class="n">squircle</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>One thing to keep in mind is that the property only has an effect when a non‑zero <code class="language-plaintext highlighter-rouge">border-radius</code> is present. Without it, the corners will remain square regardless of the <code class="language-plaintext highlighter-rouge">corner-shape</code> value.</p>

<p>And like many CSS properties, <code class="language-plaintext highlighter-rouge">corner-shape</code> is a shorthand that can accept up to four values to define different shapes for each corner individually.</p>

<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">.box</span> <span class="p">{</span>
    <span class="py">corner-top-left-shape</span><span class="p">:</span> <span class="n">scoop</span><span class="p">;</span>
    <span class="py">corner-top-right-shape</span><span class="p">:</span> <span class="n">notch</span><span class="p">;</span>
    <span class="py">corner-bottom-right-shape</span><span class="p">:</span> <span class="n">squircle</span><span class="p">;</span>
    <span class="py">corner-bottom-left-shape</span><span class="p">:</span> <span class="n">round</span><span class="p">;</span>

    <span class="c">/* or using the shorthand */</span>
    <span class="py">corner-shape</span><span class="p">:</span> <span class="n">scoop</span> <span class="n">notch</span> <span class="n">squircle</span> <span class="n">round</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>It supports smooth animation between shapes because keywords map to <code class="language-plaintext highlighter-rouge">superellipse()</code> equivalents, and browsers constrain opposite corners to avoid overlap.</p>

<h2 id="how-it-differs-from-border-radius">How it differs from border-radius</h2>

<p>The <code class="language-plaintext highlighter-rouge">border-radius</code> sets the corner’s size (<strong>how far it rounds</strong>). While the <code class="language-plaintext highlighter-rouge">corner-shape</code> sets the corner’s geometry (<strong>what kind of curve or cut it is</strong>).</p>

<p>For instance, <code class="language-plaintext highlighter-rouge">border-radius: 30px</code> with <code class="language-plaintext highlighter-rouge">corner-shape: round</code> behaves like classic rounded corners. Changing to scoop inverts the curve into a concave cut; bevel draws a straight chamfer; notch creates an inset; squircle makes superellipse-style corners—all while the radius still controls extent.</p>

<p>Apart from this, <code class="language-plaintext highlighter-rouge">corner-shape: round</code> matches the normal <code class="language-plaintext highlighter-rouge">border-radius</code> look; <code class="language-plaintext highlighter-rouge">corner-shape: square</code> effectively cancels the rounding even if a radius is set.</p>

<h2 id="some-examples">Some examples</h2>

<p>Here’s a graphic showing different <code class="language-plaintext highlighter-rouge">corner-shape</code> values applied to boxes with the same <code class="language-plaintext highlighter-rouge">border-radius</code>.</p>

<p><a href="/images/corner-shape-examples.png"><img src="/images/corner-shape-examples.png" alt="Corner shape examples" /></a></p>

<p>As you can tell, each shape gives a distinct visual style while respecting the same radius size. This opens up new design possibilities without complex SVGs or masks.</p>

<p>Try <a href="https://codepen.io/amit_merchant/pen/MYyaJOX">this CodePen</a> in a latest Chromium-based browser to see it in action.</p>

<p>Also, try this website to play around with different <code class="language-plaintext highlighter-rouge">corner-shape</code> values interactively: <a href="https://www.squircle.style/">squircle.style</a></p>

<h2 id="what-about-fallbacks">What about fallbacks?</h2>

<p>Since <code class="language-plaintext highlighter-rouge">corner-shape</code> is still experimental (currently only available in <strong>Chrome 139 and above</strong> or <strong>similar Chromium forks</strong>) and not widely supported yet, it’s important to provide fallbacks for browsers that don’t recognize it and make it a progressive enhancement.</p>

<p>Here are safe, production-ready patterns to use corner-shape with a border-radius fallback. The idea: write classic border-radius first, then layer corner-shape inside @supports so unsupported browsers keep the rounded corners.</p>

<p><strong>Basic keyword shape fallback</strong></p>

<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">.card</span> <span class="p">{</span>
  <span class="c">/* Fallback everyone supports */</span>
  <span class="nl">border-radius</span><span class="p">:</span> <span class="m">24px</span><span class="p">;</span>

  <span class="c">/* Only apply corner-shape where supported */</span>
  <span class="err">@supports</span> <span class="err">(</span><span class="py">corner-shape</span><span class="p">:</span> <span class="n">round</span><span class="p">)</span> <span class="err">{</span>
    <span class="c">/* round is the default; use a different shape */</span>
    <span class="n">corner-shape</span><span class="p">:</span> <span class="n">scoop</span><span class="p">;</span> <span class="c">/* concave corners */</span>
  <span class="p">}</span>
<span class="err">}</span>
</code></pre></div></div>

<p><strong>Per-corner values with fallback</strong></p>

<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">.badge</span> <span class="p">{</span>
  <span class="nl">border-radius</span><span class="p">:</span> <span class="m">16px</span><span class="p">;</span> <span class="c">/* fallback */</span>
<span class="p">}</span>

<span class="k">@supports</span> <span class="p">(</span><span class="n">corner-shape</span><span class="p">:</span> <span class="n">bevel</span><span class="p">)</span> <span class="p">{</span>
  <span class="nc">.badge</span> <span class="p">{</span>
    <span class="c">/* TL, TR, BR, BL shapes (clockwise) */</span>
    <span class="py">corner-shape</span><span class="p">:</span> <span class="n">bevel</span> <span class="n">notch</span> <span class="n">round</span> <span class="n">squircle</span><span class="p">;</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Using superellipse() with fallback</strong></p>

<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">.panel</span> <span class="p">{</span>
  <span class="nl">border-radius</span><span class="p">:</span> <span class="m">32px</span><span class="p">;</span> <span class="c">/* fallback */</span>
<span class="p">}</span>

<span class="k">@supports</span> <span class="p">(</span><span class="n">corner-shape</span><span class="p">:</span> <span class="n">superellipse</span><span class="p">(</span><span class="m">4</span><span class="p">))</span> <span class="p">{</span>
  <span class="nc">.panel</span> <span class="p">{</span>
    <span class="py">corner-shape</span><span class="p">:</span> <span class="n">superellipse</span><span class="p">(</span><span class="m">4</span><span class="p">);</span> <span class="c">/* squircle-like */</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Progressive enhancement inside @supports selector block</strong></p>

<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">/* default styles */</span>
<span class="nc">.box</span> <span class="p">{</span>
  <span class="nl">border-radius</span><span class="p">:</span> <span class="m">20px</span><span class="p">;</span>           <span class="c">/* fallback */</span>
  <span class="nl">background</span><span class="p">:</span> <span class="no">white</span><span class="p">;</span>
  <span class="nl">box-shadow</span><span class="p">:</span> <span class="m">0</span> <span class="m">6px</span> <span class="m">20px</span> <span class="n">rgba</span><span class="p">(</span><span class="m">0</span><span class="p">,</span><span class="m">0</span><span class="p">,</span><span class="m">0</span><span class="p">,</span><span class="m">.12</span><span class="p">);</span>
  <span class="nl">border</span><span class="p">:</span> <span class="m">1px</span> <span class="nb">solid</span> <span class="m">#e6e6e6</span><span class="p">;</span>
<span class="p">}</span>

<span class="c">/* enhance where supported */</span>
<span class="k">@supports</span> <span class="p">(</span><span class="n">corner-shape</span><span class="p">:</span> <span class="n">notch</span><span class="p">)</span> <span class="p">{</span>
  <span class="nc">.box</span> <span class="p">{</span>
    <span class="py">corner-shape</span><span class="p">:</span> <span class="n">notch</span><span class="p">;</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Some engines may support keywords but not the function. Query the specific thing you rely on:</strong></p>

<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">.card</span> <span class="p">{</span> <span class="nl">border-radius</span><span class="p">:</span> <span class="m">28px</span><span class="p">;</span> <span class="p">}</span>

<span class="k">@supports</span> <span class="p">(</span><span class="n">corner-shape</span><span class="p">:</span> <span class="n">scoop</span><span class="p">)</span> <span class="p">{</span>
  <span class="nc">.card</span> <span class="p">{</span> <span class="py">corner-shape</span><span class="p">:</span> <span class="n">scoop</span><span class="p">;</span> <span class="p">}</span>
<span class="p">}</span>

<span class="c">/* If you need the numeric control, guard the function separately */</span>
<span class="k">@supports</span> <span class="p">(</span><span class="n">corner-shape</span><span class="p">:</span> <span class="n">superellipse</span><span class="p">(</span><span class="m">6</span><span class="p">))</span> <span class="p">{</span>
  <span class="nc">.card</span> <span class="p">{</span> <span class="py">corner-shape</span><span class="p">:</span> <span class="n">superellipse</span><span class="p">(</span><span class="m">6</span><span class="p">);</span> <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="best-practices">Best practices</h2>

<p>Here are some tips for using <code class="language-plaintext highlighter-rouge">corner-shape</code> effectively:</p>

<ul>
  <li>
    <p><strong>Order matters:</strong> Always declare border-radius first, then corner-shape inside @supports. Unsupported browsers ignore the @supports block and keep the radius.</p>
  </li>
  <li>
    <p><strong>Dependency:</strong> corner-shape only has an effect when border-radius is non-zero. Keep a radius in your fallback.</p>
  </li>
  <li>
    <p><strong>Resetting:</strong> corner-shape: square effectively removes rounding; do not use it if you want a rounded fallback.</p>
  </li>
  <li>
    <p><strong>Animation fallbacks:</strong> If you animate corner-shape, also include a non-animated border-radius experience, or animate border-radius as a simpler fallback.</p>
  </li>
  <li>
    <p><strong>Granular queries:</strong> If you target logical shorthands (e.g., corner-start-start-shape), guard those in @supports too, since support may differ from the main shorthand.</p>
  </li>
  <li>
    <p><strong>Server-side rendering/theming:</strong> Prefer simple keyword shapes for broader compatibility; reserve <code class="language-plaintext highlighter-rouge">superellipse()</code> for environments where you’ve verified support.</p>
  </li>
</ul>]]></content><author><name>Amit Merchant</name></author><category term="CSS" /><summary type="html"><![CDATA[For years, we’ve faked “characterful” corners with SVG masks, pseudo-elements, and more than a few headaches. The problem? Border-radius only controls size, not shape.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.amitmerchant.com/cdn/the-modern-way-to-draw-squircles-using-corner-shape-in-css.png" /><media:content medium="image" url="https://www.amitmerchant.com/cdn/the-modern-way-to-draw-squircles-using-corner-shape-in-css.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">The new, standards‑compliant URI/URL API in PHP 8.5</title><link href="https://www.amitmerchant.com/the-new-standards-compliant-uri-url-api-in-php-85/" rel="alternate" type="text/html" title="The new, standards‑compliant URI/URL API in PHP 8.5" /><published>2025-10-19T00:00:00+00:00</published><updated>2025-10-19T00:00:00+00:00</updated><id>https://www.amitmerchant.com/the-new-standards-compliant-uri-url-api-in-php-85</id><content type="html" xml:base="https://www.amitmerchant.com/the-new-standards-compliant-uri-url-api-in-php-85/"><![CDATA[<p>PHP’s <a href="https://www.php.net/manual/en/function.parse-url.php">parse_url</a> was not compliant with <strong>RFC 3986</strong> (generic, permissive URIs) or <strong>WHATWG URL</strong> (browser-style URLs), leading to inconsistent behavior, interoperability problems with tools like cURL, and subtle security issues such as parsing confusion.</p>

<p>To fix these issues, PHP 8.5 introduces a standardized API so URIs/URLs are parsed, normalized, modified, and compared consistently with the relevant specs.</p>

<hr />

<p>PHP now ships two immutable classes for parsing and working with addresses.</p>

<ul>
  <li>
    <p><strong>Uri\Rfc3986\Uri:</strong> follows <a href="https://datatracker.ietf.org/doc/html/rfc3986">RFC 3986</a> (generic URIs, strict validation, optional normalization, percent‑decoding where safe)</p>
  </li>
  <li>
    <p><strong>Uri\WhatWg\Url:</strong> follows <a href="https://url.spec.whatwg.org/">WHATWG URL</a> (browser behavior, IDNA/Unicode hosts, parse‑time transformations, soft/hard errors)</p>
  </li>
</ul>

<p>Both support parsing, resolving relative references, getting/setting components, comparing, and serializing.</p>

<ul id="markdown-toc">
  <li><a href="#whats-the-difference-between-the-two-classes" id="markdown-toc-whats-the-difference-between-the-two-classes">What’s the difference between the two classes?</a></li>
  <li><a href="#how-these-classes-work" id="markdown-toc-how-these-classes-work">How these classes work?</a></li>
  <li><a href="#a-real-world-example" id="markdown-toc-a-real-world-example">A real-world example</a></li>
  <li><a href="#in-closing" id="markdown-toc-in-closing">In closing</a></li>
</ul>

<h2 id="whats-the-difference-between-the-two-classes">What’s the difference between the two classes?</h2>

<p>The classes differ in the specifications they implement and how they handle parsing, normalization, and error reporting.</p>

<ul>
  <li>
    <p>RFC 3986 accepts URIs (may be scheme‑less), is strict on invalid characters, and offers <strong>“raw”</strong> vs <strong>“normalized‑decoded”</strong> views.</p>
  </li>
  <li>
    <p>WHATWG only handles URLs (needs a scheme), auto‑normalizes on parse, keeps percent‑encoding in most components, and reports soft errors.</p>
  </li>
</ul>

<h2 id="how-these-classes-work">How these classes work?</h2>

<p>These classes provide a clean, object-oriented way to work with URIs and URLs. You can create instances by parsing strings, access individual components, modify them immutably, and serialize back to strings.</p>

<p><strong>Here’s how you can use them to parse and safely validate a URL.</strong></p>

<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">use</span> <span class="nc">Uri\Rfc3986\Uri</span><span class="p">;</span>
<span class="kn">use</span> <span class="nc">Uri\WhatWg\Url</span><span class="p">;</span>

<span class="nv">$rfc</span> <span class="o">=</span> <span class="nc">Uri</span><span class="o">::</span><span class="nf">parse</span><span class="p">(</span><span class="s2">"https://example.com/path?x=1"</span><span class="p">);</span>   <span class="c1">// Uri or null</span>
<span class="nv">$whatwg</span> <span class="o">=</span> <span class="nc">Url</span><span class="o">::</span><span class="nf">parse</span><span class="p">(</span><span class="s2">"https://example.com/path?x=1"</span><span class="p">);</span> <span class="c1">// Url or null</span>

<span class="nv">$bad</span> <span class="o">=</span> <span class="nc">Uri</span><span class="o">::</span><span class="nf">parse</span><span class="p">(</span><span class="s2">"invalid uri"</span><span class="p">);</span>                     <span class="c1">// null (strict failure)</span>
<span class="nv">$errors</span> <span class="o">=</span> <span class="p">[];</span>
<span class="nv">$bad2</span> <span class="o">=</span> <span class="nc">Url</span><span class="o">::</span><span class="nf">parse</span><span class="p">(</span><span class="s2">" invalid url"</span><span class="p">,</span> <span class="kc">null</span><span class="p">,</span> <span class="nv">$errors</span><span class="p">);</span>    <span class="c1">// null with soft/hard error details</span>
</code></pre></div></div>

<p>WHATWG surfaces soft errors while still succeeding:</p>

<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">use</span> <span class="nc">Uri\WhatWg\Url</span><span class="p">;</span>

<span class="nv">$soft</span> <span class="o">=</span> <span class="p">[];</span>
<span class="nv">$url</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Url</span><span class="p">(</span><span class="s2">" https://example.org"</span><span class="p">,</span> <span class="kc">null</span><span class="p">,</span> <span class="nv">$soft</span><span class="p">);</span>
<span class="k">echo</span> <span class="nv">$url</span><span class="o">-&gt;</span><span class="nf">toAsciiString</span><span class="p">();</span> <span class="c1">// https://example.org</span>
<span class="c1">// $soft[0]-&gt;type === UrlValidationErrorType::InvalidUrlUnit</span>
</code></pre></div></div>

<p><strong>Here’s how you can fetch URL components components (RFC raw vs normalized‑decoded).</strong></p>

<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">use</span> <span class="nc">Uri\Rfc3986\Uri</span><span class="p">;</span>

<span class="nv">$u</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Uri</span><span class="p">(</span><span class="s2">"https://%61pple:p%61ss@ex%61mple.com:433/foob%61r?%61bc=%61bc#%61bc"</span><span class="p">);</span>

<span class="k">echo</span> <span class="nv">$u</span><span class="o">-&gt;</span><span class="nf">getRawHost</span><span class="p">();</span>     <span class="c1">// ex%61mple.com</span>
<span class="k">echo</span> <span class="nv">$u</span><span class="o">-&gt;</span><span class="nf">getHost</span><span class="p">();</span>        <span class="c1">// example.com</span>

<span class="k">echo</span> <span class="nv">$u</span><span class="o">-&gt;</span><span class="nf">getRawPath</span><span class="p">();</span>     <span class="c1">// /foob%61r</span>
<span class="k">echo</span> <span class="nv">$u</span><span class="o">-&gt;</span><span class="nf">getPath</span><span class="p">();</span>        <span class="c1">// /foobar</span>

<span class="k">echo</span> <span class="nv">$u</span><span class="o">-&gt;</span><span class="nf">getRawUserInfo</span><span class="p">();</span> <span class="c1">// %61pple:p%61ss</span>
<span class="k">echo</span> <span class="nv">$u</span><span class="o">-&gt;</span><span class="nf">getUserInfo</span><span class="p">();</span>    <span class="c1">// apple:pass</span>
</code></pre></div></div>

<p>WHATWG returns a single normalized view for most components and keeps encoding:</p>

<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">use</span> <span class="nc">Uri\WhatWg\Url</span><span class="p">;</span>

<span class="nv">$w</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Url</span><span class="p">(</span><span class="s2">"HTTPS://%61pple:p%61ss@ex%61mple.com:433/foob%61r?%61bc=%61bc#%61bc"</span><span class="p">);</span>

<span class="k">echo</span> <span class="nv">$w</span><span class="o">-&gt;</span><span class="nf">getScheme</span><span class="p">();</span>      <span class="c1">// https</span>
<span class="k">echo</span> <span class="nv">$w</span><span class="o">-&gt;</span><span class="nf">getUsername</span><span class="p">();</span>    <span class="c1">// %61pple</span>
<span class="k">echo</span> <span class="nv">$w</span><span class="o">-&gt;</span><span class="nf">getPath</span><span class="p">();</span>        <span class="c1">// /foob%61r</span>
<span class="k">echo</span> <span class="nv">$w</span><span class="o">-&gt;</span><span class="nf">getQuery</span><span class="p">();</span>       <span class="c1">// %61bc=%61bc</span>
</code></pre></div></div>

<p><strong>IDNA and Unicode hosts (WHATWG only)</strong></p>

<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">use</span> <span class="nc">Uri\WhatWg\Url</span><span class="p">;</span>

<span class="nv">$w</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Url</span><span class="p">(</span><span class="s2">"https://🐘.com"</span><span class="p">);</span>
<span class="k">echo</span> <span class="nv">$w</span><span class="o">-&gt;</span><span class="nf">getAsciiHost</span><span class="p">();</span>   <span class="c1">// xn--go8h.com</span>
<span class="k">echo</span> <span class="nv">$w</span><span class="o">-&gt;</span><span class="nf">getUnicodeHost</span><span class="p">();</span> <span class="c1">// 🐘.com</span>
</code></pre></div></div>

<p><strong>Here’s how you can recompose the URL components back into a string.</strong></p>

<p>RFC raw vs normalized:</p>

<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">use</span> <span class="nc">Uri\Rfc3986\Uri</span><span class="p">;</span>

<span class="nv">$u</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Uri</span><span class="p">(</span><span class="s2">"HTTPS://EXAMPLE.com"</span><span class="p">);</span>
<span class="k">echo</span> <span class="nv">$u</span><span class="o">-&gt;</span><span class="nf">toRawString</span><span class="p">();</span> <span class="c1">// HTTPS://EXAMPLE.com</span>
<span class="k">echo</span> <span class="nv">$u</span><span class="o">-&gt;</span><span class="nf">toString</span><span class="p">();</span>    <span class="c1">// https://example.com</span>
</code></pre></div></div>

<p>WHATWG ASCII vs Unicode:</p>

<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">use</span> <span class="nc">Uri\WhatWg\Url</span><span class="p">;</span>

<span class="nv">$w</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Url</span><span class="p">(</span><span class="s2">"HTTPS://////你好你好.com"</span><span class="p">);</span>
<span class="k">echo</span> <span class="nv">$w</span><span class="o">-&gt;</span><span class="nf">toAsciiString</span><span class="p">();</span>   <span class="c1">// https://xn--6qqa088eba.com/</span>
<span class="k">echo</span> <span class="nv">$w</span><span class="o">-&gt;</span><span class="nf">toUnicodeString</span><span class="p">();</span> <span class="c1">// https://你好你好.com/</span>
</code></pre></div></div>

<p><strong>You can also check equality between two URIs/URLs.</strong></p>

<p>By default, fragments are excluded.</p>

<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">use</span> <span class="nc">Uri\Rfc3986\Uri</span><span class="p">;</span>
<span class="kn">use</span> <span class="nc">Uri\WhatWg\Url</span><span class="p">;</span>

<span class="nv">$u1</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Uri</span><span class="p">(</span><span class="s2">"https://example.COM#foo"</span><span class="p">);</span>
<span class="nv">$u2</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Uri</span><span class="p">(</span><span class="s2">"https://EXAMPLE.COM"</span><span class="p">);</span>
<span class="nb">var_dump</span><span class="p">(</span><span class="nv">$u1</span><span class="o">-&gt;</span><span class="nf">equals</span><span class="p">(</span><span class="nv">$u2</span><span class="p">));</span> <span class="c1">// true</span>

<span class="nv">$w1</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Url</span><span class="p">(</span><span class="s2">"https:////example.COM/"</span><span class="p">);</span>
<span class="nv">$w2</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Url</span><span class="p">(</span><span class="s2">"https://EXAMPLE.COM"</span><span class="p">);</span>
<span class="nb">var_dump</span><span class="p">(</span><span class="nv">$w1</span><span class="o">-&gt;</span><span class="nf">equals</span><span class="p">(</span><span class="nv">$w2</span><span class="p">));</span> <span class="c1">// true</span>
</code></pre></div></div>

<p>Include fragments when you need strict match:</p>

<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">use</span> <span class="nc">Uri\Rfc3986\Uri</span><span class="p">;</span>

<span class="nv">$u</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Uri</span><span class="p">(</span><span class="s2">"https://example.com#foo"</span><span class="p">);</span>
<span class="nb">var_dump</span><span class="p">(</span>
    <span class="nv">$u</span><span class="o">-&gt;</span><span class="nf">equals</span><span class="p">(</span>
        <span class="k">new</span> <span class="nc">Uri</span><span class="p">(</span><span class="s2">"https://example.com"</span><span class="p">),</span> 
        <span class="nc">Uri\ComparisonMode</span><span class="o">::</span><span class="nc">IncludeFragment</span>
    <span class="p">)</span>
<span class="p">);</span> 
<span class="c1">// false</span>
</code></pre></div></div>

<blockquote class="you-may-like">
  <p><strong>You may like →</strong> <a href="/everything-that-is-coming-in-php-85/">Everything that is coming in PHP 8.5</a></p>
</blockquote>

<h2 id="a-real-world-example">A real-world example</h2>

<p>Here’s a simple redirect-and-validate example using the new URI API.</p>

<p>Redirect HTTP to HTTPS and safely keep the path and query:</p>

<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">use</span> <span class="nc">Uri\Rfc3986\Uri</span><span class="p">;</span>
<span class="kn">use</span> <span class="nc">Uri\UriComparisonMode</span><span class="p">;</span>

<span class="c1">// Imagine this comes from an incoming request URL you need to validate and normalize</span>
<span class="nv">$incoming</span> <span class="o">=</span> <span class="s2">"http://Example.COM/products/list?page=1&amp;sort=price%2Bdesc"</span><span class="p">;</span>

<span class="c1">// 1) Parse and validate strictly (RFC 3986)</span>
<span class="nv">$uri</span> <span class="o">=</span> <span class="nc">Uri</span><span class="o">::</span><span class="nf">parse</span><span class="p">(</span><span class="nv">$incoming</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="nv">$uri</span> <span class="o">===</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span>
    <span class="nb">http_response_code</span><span class="p">(</span><span class="mi">400</span><span class="p">);</span>
    <span class="k">exit</span><span class="p">(</span><span class="s2">"Bad URL"</span><span class="p">);</span>
<span class="p">}</span>

<span class="c1">// 2) Normalize for consistent routing/logging (lowercase host, clean path)</span>
<span class="nv">$normalized</span> <span class="o">=</span> <span class="nv">$uri</span><span class="o">-&gt;</span><span class="nf">toString</span><span class="p">();</span> <span class="c1">// https://example.com/... once we change scheme below</span>
<span class="nb">error_log</span><span class="p">(</span><span class="s2">"Normalized request: "</span> <span class="mf">.</span> <span class="nv">$normalized</span><span class="p">);</span>

<span class="c1">// 3) Enforce HTTPS by immutably changing just the scheme</span>
<span class="nv">$secure</span> <span class="o">=</span> <span class="nv">$uri</span><span class="o">-&gt;</span><span class="nf">withScheme</span><span class="p">(</span><span class="s2">"https"</span><span class="p">);</span>

<span class="c1">// 4) Preserve path and query exactly as provided (safe for caches/signing)</span>
<span class="nv">$target</span> <span class="o">=</span> <span class="nv">$secure</span><span class="o">-&gt;</span><span class="nf">toString</span><span class="p">();</span> <span class="c1">// normalized RFC 3986 string</span>

<span class="c1">// 5) Compare ignoring fragment (default) to avoid duplicate redirects</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nv">$secure</span><span class="o">-&gt;</span><span class="nf">equals</span><span class="p">(</span><span class="nv">$uri</span><span class="p">,</span> <span class="nc">UriComparisonMode</span><span class="o">::</span><span class="nc">ExcludeFragment</span><span class="p">))</span> <span class="p">{</span>
    <span class="nb">header</span><span class="p">(</span><span class="s2">"Location: "</span> <span class="mf">.</span> <span class="nv">$target</span><span class="p">,</span> <span class="kc">true</span><span class="p">,</span> <span class="mi">301</span><span class="p">);</span>
    <span class="k">exit</span><span class="p">;</span>
<span class="p">}</span>

<span class="c1">// Continue serving the HTTPS request...</span>
</code></pre></div></div>

<h2 id="in-closing">In closing</h2>

<p>Standards compliance fixes subtle bugs and security issues, like the parsing confusion exploited when <code class="language-plaintext highlighter-rouge">FILTER_VALIDATE_URL</code> disagrees with a client that follows RFC 3986.</p>

<p>The clear, immutable API reduce surprises; normalized views help routing/caching, while raw views preserve exact bytes for signing or proxying. WHATWG alignment matches browser behavior, including Unicode domains and automatic percent‑encoding.</p>

<p>You can read more about the new URI/URL API in the <a href="https://wiki.php.net/rfc/url_parsing_api">official RFC</a>.</p>]]></content><author><name>Amit Merchant</name></author><category term="PHP" /><summary type="html"><![CDATA[PHP’s parse_url was not compliant with RFC 3986 (generic, permissive URIs) or WHATWG URL (browser-style URLs), leading to inconsistent behavior, interoperability problems with tools like cURL, and subtle security issues such as parsing confusion.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.amitmerchant.com/cdn/the-new-standards-compliant-uri-url-api-in-php-85.png" /><media:content medium="image" url="https://www.amitmerchant.com/cdn/the-new-standards-compliant-uri-url-api-in-php-85.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">The new progress() function in CSS</title><link href="https://www.amitmerchant.com/the-progress-function-css/" rel="alternate" type="text/html" title="The new progress() function in CSS" /><published>2025-10-08T00:00:00+00:00</published><updated>2025-10-08T00:00:00+00:00</updated><id>https://www.amitmerchant.com/the-progress-function-css</id><content type="html" xml:base="https://www.amitmerchant.com/the-progress-function-css/"><![CDATA[<p>Imagine a responsive hero image that becomes more transparent as the viewport gets narrower, helping text readability on small screens or a card that scales up slightly as the viewport grows, adding a subtle polish.</p>

<p>Until now, achieving these effects required complex <code class="language-plaintext highlighter-rouge">calc()</code> expressions or JavaScript, often leading to brittle solutions. But with the new CSS <code class="language-plaintext highlighter-rouge">progress()</code> function, you can express these relationships cleanly and intuitively.</p>

<p>With the new CSS <code class="language-plaintext highlighter-rouge">progress()</code> function, you can express <strong>“how far”</strong> the viewport width has moved between a minimum and maximum size, and map that directly to opacity.</p>

<blockquote>
  <p>The progress function returns how far a value is between two bounds.</p>
</blockquote>

<p>Here’s what the function definition looks like:</p>

<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">progress</span><span class="p">(</span><span class="o">&lt;</span><span class="n">value</span><span class="o">&gt;</span><span class="p">,</span> <span class="o">&lt;</span><span class="n">start</span><span class="o">&gt;</span><span class="p">,</span> <span class="o">&lt;</span><span class="n">end</span><span class="o">&gt;</span><span class="p">)</span>
</code></pre></div></div>

<p>Here, <code class="language-plaintext highlighter-rouge">&lt;value&gt;</code> is the current value you want to evaluate, <code class="language-plaintext highlighter-rouge">&lt;start&gt;</code> is the minimum bound, and <code class="language-plaintext highlighter-rouge">&lt;end&gt;</code> is the maximum bound. The function returns a number between <code class="language-plaintext highlighter-rouge">0</code> and <code class="language-plaintext highlighter-rouge">1</code>, where <code class="language-plaintext highlighter-rouge">0</code> means the value is at or below the start, and <code class="language-plaintext highlighter-rouge">1</code> means it’s at or above the end. Values in between are represented as a decimal between <code class="language-plaintext highlighter-rouge">0</code> and <code class="language-plaintext highlighter-rouge">1</code>.</p>

<h2 id="where-can-you-use-it">Where can you use it?</h2>

<p>You can use <code class="language-plaintext highlighter-rouge">progress()</code> wherever you need a normalized 0–1 value from mixed units. You can treat progress() as a unit-agnostic “interpolator” for responsive and state-driven effects. Common scenarios include:</p>

<ul>
  <li>
    <p><strong>Visual polish tied to viewport size:</strong> fade images, adjust blur, tweak letter-spacing, or scale components as 100vw moves between small and large breakpoints, without media queries.</p>
  </li>
  <li>
    <p><strong>Adaptive layout thresholds:</strong> smoothly ramp padding, gap, border-radius, or container width between min/max sizes so UI densifies on small screens and breathes on large ones.</p>
  </li>
  <li>
    <p><strong>Dynamic spacing and alignment:</strong> adjust margins, padding, or alignment based on viewport size to create a more fluid and responsive layout.</p>
  </li>
  <li>
    <p><strong>Scroll and view-based choreography:</strong> combine with scroll-driven animations or animation-timeline to map element opacity, transform, or filter intensity to how far a section has scrolled into view.</p>
  </li>
  <li>
    <p><strong>Component states and data-driven styling:</strong> map numeric CSS variables (e.g., a rating 0–100, audio volume 0–1, or battery level in px) to opacity, color stops, or thickness, even when inputs use different units.</p>
  </li>
  <li>
    <p><strong>Fluid typography and readability:</strong> blend font-size, word/letter-spacing, and line-height as the viewport changes for better legibility; tune text-shadow or background-overlay strength to maintain contrast.</p>
  </li>
  <li>
    <p><strong>Theme transitions:</strong> interpolate between two colors using color-mix or relative color syntax, using progress() as the mixing ratio for smooth dark/light adjustments across sizes.</p>
  </li>
  <li>
    <p><strong>Micro-interactions without JS:</strong> subtle scale/tilt on cards, emphasis rings via outline-width, or highlight intensity via filter: brightness, all tied to a single progress() range.</p>
  </li>
  <li>
    <p><strong>Performance-friendly fallbacks:</strong> when calc() can’t mix units, progress() bridges px, rem, vw so you can clamp the result and avoid complex JS or custom properties gymnastics.</p>
  </li>
</ul>

<blockquote>
  <p><strong>Fun fact:</strong> The <code class="language-plaintext highlighter-rouge">progress()</code> function is similar to the <code class="language-plaintext highlighter-rouge">smoothstep()</code> <a href="https://en.wikipedia.org/wiki/Smoothstep">functions</a> in shaders and game engines, which is used to interpolate values smoothly between two points. Languages like GLSL, HLSL, and Unity’s ShaderLab have had this function for years.</p>
</blockquote>

<h2 id="a-practical-example">A practical example</h2>

<p>Here’s a practical snippet that fades the image from fully visible on large screens to slightly transparent on small screens.</p>

<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">.hero</span> <span class="nt">img</span> <span class="p">{</span>
  <span class="c">/* As viewport goes from 1200px down to 480px, opacity eases from 1 to 0.4 */</span>
  <span class="nl">opacity</span><span class="p">:</span> <span class="n">clamp</span><span class="p">(</span><span class="m">0.4</span><span class="p">,</span> <span class="m">1</span> <span class="n">-</span> <span class="n">progress</span><span class="p">(</span><span class="m">100vw</span><span class="p">,</span> <span class="m">480px</span><span class="p">,</span> <span class="m">1200px</span><span class="p">)</span> <span class="err">*</span> <span class="m">0.6</span><span class="p">,</span> <span class="m">1</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>In this example:</p>

<ul>
  <li>At widths ≤ 480px, <code class="language-plaintext highlighter-rouge">progress(100vw, 480px, 1200px)</code> is 0, so opacity is 1 − 0 × 0.6 = 1.</li>
  <li>At widths ≥ 1200px, progress is 1, so opacity is 1 − 1 × 0.6 = 0.4.</li>
  <li>Between those, it smoothly interpolates, even though <code class="language-plaintext highlighter-rouge">100vw</code> is in viewport units and the bounds are in pixels — a key advantage over <code class="language-plaintext highlighter-rouge">calc()</code>.</li>
</ul>

<p><strong>Another real-world use:</strong> scale a card slightly as the viewport grows, without media queries:</p>

<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">.card</span> <span class="p">{</span>
  <span class="nl">transform</span><span class="p">:</span> <span class="n">scale</span><span class="p">(</span><span class="n">calc</span><span class="p">(</span><span class="m">1</span> <span class="err">+</span> <span class="n">progress</span><span class="p">(</span><span class="m">100vw</span><span class="p">,</span> <span class="m">360px</span><span class="p">,</span> <span class="m">1024px</span><span class="p">)</span> <span class="err">*</span> <span class="m">0.05</span><span class="p">));</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Here, the card scales from <strong>1.00</strong> at <strong>360px</strong> to about <strong>1.05</strong> at <strong>1024px</strong>, giving a subtle, device-size-aware polish.</p>

<p>I put together the above examples in this <a href="https://codepen.io/amit_merchant/pen/ByjRyBp">CodePen</a> so you can see them in action.</p>

<p class="codepen" data-height="300" data-slug-hash="ByjRyBp" data-pen-title="The progress() function example" data-user="amit_merchant" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/amit_merchant/pen/ByjRyBp">
  The progress() function example</a> by Amit Merchant (<a href="https://codepen.io/amit_merchant">@amit_merchant</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async="" src="https://public.codepenassets.com/embed/index.js"></script>

<h2 id="browser-support">Browser support</h2>

<p>As of today, the CSS <code class="language-plaintext highlighter-rouge">progress()</code> function is available in <strong>modern Chromium browsers</strong> and <strong>Safari 26</strong>. Firefox does not currently list support for <code class="language-plaintext highlighter-rouge">progress()</code> in its CSS value functions yet.</p>

<p>So, you can fallback to using <code class="language-plaintext highlighter-rouge">clamp()</code> or <code class="language-plaintext highlighter-rouge">calc()</code> in scenarios where you need to support Firefox, for now (e.g., <code class="language-plaintext highlighter-rouge">clamp(calc((value-start)/(end-start)), 0, 1)</code> where units permit).</p>]]></content><author><name>Amit Merchant</name></author><category term="CSS" /><summary type="html"><![CDATA[Imagine a responsive hero image that becomes more transparent as the viewport gets narrower, helping text readability on small screens or a card that scales up slightly as the viewport grows, adding a subtle polish.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.amitmerchant.com/cdn/the-progress-function-css.png" /><media:content medium="image" url="https://www.amitmerchant.com/cdn/the-progress-function-css.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>