<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Leo</title>
    <description>The latest articles on DEV Community by Leo (@leobaniak).</description>
    <link>https://dev.to/leobaniak</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3997121%2F061e76e1-0a08-4d9d-827c-24f1e8b9e15b.png</url>
      <title>DEV Community: Leo</title>
      <link>https://dev.to/leobaniak</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/leobaniak"/>
    <language>en</language>
    <item>
      <title>Safari 27.1 previews CSS for foldable screens</title>
      <dc:creator>Leo</dc:creator>
      <pubDate>Sun, 25 Oct 2026 16:07:45 +0000</pubDate>
      <link>https://dev.to/leobaniak/safari-271-previews-css-for-foldable-screens-5de</link>
      <guid>https://dev.to/leobaniak/safari-271-previews-css-for-foldable-screens-5de</guid>
      <description>&lt;p&gt;I don't have a foldable phone on my desk, so this one started with WebKit's code samples pasted into a scratch stylesheet. The news: Safari 27.1 ships alongside the iPhone Duo, running what WebKit calls "the same WebKit engine and the same web technologies as every other iPhone." It also includes a preview of two APIs for foldable layouts, the Device Posture API and the Viewport Segments API. Both sit behind feature flags. WebKit's post doesn't mention other browsers, a spec stage or Baseline status, so treat this as a developer preview and nothing more.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two new questions your CSS can ask
&lt;/h2&gt;

&lt;p&gt;The iPhone Duo has an outer display for when it's closed and a larger inner display for when it's open. Both rotate between portrait and landscape. Per WebKit, that puts viewport widths anywhere from 382 to 951 CSS pixels and heights from 368 to 902.&lt;/p&gt;

&lt;p&gt;Width and height can't tell you there's a fold running through the screen. The two previews can.&lt;/p&gt;

&lt;p&gt;The Device Posture API tells you how the device is being held. It reports one of two values. &lt;code&gt;folded&lt;/code&gt; means the device is partially folded, in a book or laptop pose. &lt;code&gt;continuous&lt;/code&gt; means the screen is flat, which per WebKit covers both fully open and fully closed.&lt;/p&gt;

&lt;p&gt;The Viewport Segments API tells you how the viewport is split. It adds the &lt;code&gt;horizontal-viewport-segments&lt;/code&gt; and &lt;code&gt;vertical-viewport-segments&lt;/code&gt; media queries, plus six environment variables: &lt;code&gt;viewport-segment-width&lt;/code&gt;, &lt;code&gt;viewport-segment-height&lt;/code&gt;, &lt;code&gt;viewport-segment-top&lt;/code&gt;, &lt;code&gt;viewport-segment-right&lt;/code&gt;, &lt;code&gt;viewport-segment-bottom&lt;/code&gt; and &lt;code&gt;viewport-segment-left&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wiring up a two-pane layout
&lt;/h2&gt;

&lt;p&gt;The smallest useful rule comes straight from WebKit's post. Go to two columns when the device is folded:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@media&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;device-posture&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;folded&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;.layout&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;grid-template-columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1fr&lt;/span&gt; &lt;span class="m"&gt;1fr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That works, but &lt;code&gt;1fr 1fr&lt;/code&gt; is a guess about where the fold sits. The segment variables let the grid match the physical halves. Each &lt;code&gt;env()&lt;/code&gt; call takes a column index and a row index, counted from 0 at the top left. With two segments side by side, the left one is &lt;code&gt;0 0&lt;/code&gt; and the right one is &lt;code&gt;1 0&lt;/code&gt;. Stacked, the top is &lt;code&gt;0 0&lt;/code&gt; and the bottom is &lt;code&gt;0 1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;horizontal-viewport-segments: 2&lt;/code&gt; matches the book pose, with two segments side by side. &lt;code&gt;vertical-viewport-segments: 2&lt;/code&gt; matches the laptop pose, with two stacked. Here's WebKit's book-pose grid:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@media&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;horizontal-viewport-segments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;.wrapper&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;grid-template-columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;viewport-segment-width&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;viewport-segment-width&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the script side, &lt;code&gt;navigator.devicePosture.type&lt;/code&gt; gives you the current posture and fires a &lt;code&gt;change&lt;/code&gt; event. &lt;code&gt;window.viewport.segments&lt;/code&gt; is an array of &lt;code&gt;DOMRect&lt;/code&gt; objects. WebKit's sample logs each segment's size and runs again on both posture changes and &lt;code&gt;resize&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;reportSegments&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;viewport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;segments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;segment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Segment &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;segment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;px × &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;segment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;px`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;reportSegments&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;devicePosture&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;change&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reportSegments&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;resize&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reportSegments&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The rough edges
&lt;/h2&gt;

&lt;p&gt;The flags come first. To try any of this, open Settings &amp;gt; Apps &amp;gt; Safari &amp;gt; Advanced &amp;gt; Feature Flags and turn both APIs on. Your visitors won't have done that. WebKit also says, with both APIs behind flags, "there may still be time to evolve how they work." I wouldn't build a layout that depends on these exact names yet.&lt;/p&gt;

&lt;p&gt;Second, don't sniff for the device. WebKit puts it bluntly: "Avoid UA sniffing to detect devices, and use feature detection instead." The post points to the usual responsive toolkit of media queries, Grid and Flexbox. The rules above already behave that way. Where the query doesn't match, the page keeps its normal layout.&lt;/p&gt;

&lt;p&gt;Third, &lt;code&gt;continuous&lt;/code&gt; covers both the open inner display and the closed outer one. Posture alone won't tell those two apart. Your existing width-based queries still have to handle that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Support today
&lt;/h2&gt;

&lt;p&gt;Safari 27.1 on the iPhone Duo, behind flags. That's the full list as far as WebKit's post goes. It names no other engine, no standards status and no Baseline status. If you don't have the hardware, WebKit says Xcode 27.1 or later includes Device Hub, where you can create an iPhone Duo simulator. One detail outside CSS: when the device is partially folded like a book, Safari 27.1 shows PDFs as two pages side by side.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'm watching next
&lt;/h2&gt;

&lt;p&gt;Whether the names hold. WebKit has said these APIs can still change while they're flagged. In the next Safari release notes, the first thing I'll check is whether &lt;code&gt;device-posture&lt;/code&gt; and the &lt;code&gt;viewport-segment-*&lt;/code&gt; variables come out from behind the flag unchanged. Until then, your plain responsive layout is the one that ships.&lt;/p&gt;

</description>
      <category>safari</category>
      <category>webkit</category>
      <category>deviceposture</category>
      <category>viewportsegments</category>
    </item>
    <item>
      <title>Grid Lanes masonry settles on Grid syntax, and flow-tolerance is now fit-tolerance</title>
      <dc:creator>Leo</dc:creator>
      <pubDate>Sat, 24 Oct 2026 16:07:14 +0000</pubDate>
      <link>https://dev.to/leobaniak/grid-lanes-masonry-settles-on-grid-syntax-and-flow-tolerance-is-now-fit-tolerance-dm4</link>
      <guid>https://dev.to/leobaniak/grid-lanes-masonry-settles-on-grid-syntax-and-flow-tolerance-is-now-fit-tolerance-dm4</guid>
      <description>&lt;p&gt;If you wrote &lt;code&gt;flow-tolerance&lt;/code&gt; somewhere in a Grid Lanes prototype, rename it. Saleh Mubashar's walkthrough on Piccalilli, published 22 October, says the property is now &lt;code&gt;fit-tolerance&lt;/code&gt;. The layout mode itself is &lt;code&gt;display: grid-lanes&lt;/code&gt;, and according to the article it works in Safari 26.4 and later, while "support in other browsers is still evolving."&lt;/p&gt;

&lt;p&gt;The smallest version is three declarations, and they're all ones you already know from Grid:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.masonry&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;grid-lanes&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;grid-template-columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;repeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;auto-fill&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;minmax&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;14rem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1fr&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="nl"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That reuse is deliberate, and it took a while to get here. Rachel Andrew opened a CSSWG issue asking for a native Pinterest-style layout in 2017. Two competing shapes followed: &lt;code&gt;grid-template-rows: masonry&lt;/code&gt; on a regular grid (Mozilla's idea, later picked up by WebKit) and a separate &lt;code&gt;display: masonry&lt;/code&gt; mode from Chromium. In January 2025 the CSS Working Group agreed to reuse Grid's track-definition syntax, and the article says the name &lt;code&gt;display: grid-lanes&lt;/code&gt; was settled in December 2025. Spanning works the Grid way too: &lt;code&gt;grid-column: span 2&lt;/code&gt; on an item.&lt;/p&gt;

&lt;h2&gt;
  
  
  The knob that got renamed
&lt;/h2&gt;

&lt;p&gt;The browser places each item in a lane, usually the shortest one. &lt;code&gt;fit-tolerance&lt;/code&gt; decides what counts as "shortest" when lanes are close in height. Per the source, &lt;code&gt;0&lt;/code&gt; favours the tightest packing. A larger value lets the browser treat near-equal lanes as equal, so items stay closer to source order, and &lt;code&gt;infinite&lt;/code&gt; takes that all the way. The default is &lt;code&gt;normal&lt;/code&gt;, which resolves to &lt;code&gt;1em&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.masonry&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;fit-tolerance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the one thing I'd try today. Open a card grid in Safari, flip between &lt;code&gt;0&lt;/code&gt; and &lt;code&gt;infinite&lt;/code&gt;, and watch where the cards land. The article also notes that source order stays intact even when items move around visually, so it's worth checking keyboard order on your own content.&lt;/p&gt;

&lt;p&gt;The caveat is the support line. One engine ships it, so for now this is progressive enhancement: browsers that don't recognise &lt;code&gt;grid-lanes&lt;/code&gt; ignore that declaration, which means your fallback layout has to come from a declaration they do understand. What I'm watching next is whether the other engines ship it under the settled name, and how &lt;code&gt;subgrid&lt;/code&gt; cards inside a Grid Lanes container behave in practice. The article says subgrid can work alongside it, since masonry now lives inside the Grid model.&lt;/p&gt;

</description>
      <category>css</category>
      <category>gridlanes</category>
      <category>masonry</category>
      <category>layout</category>
    </item>
    <item>
      <title>Vercel wants static credentials gone. Its own GitHub Actions deploy still needs one</title>
      <dc:creator>Leo</dc:creator>
      <pubDate>Sat, 24 Oct 2026 10:23:51 +0000</pubDate>
      <link>https://dev.to/leobaniak/vercel-wants-static-credentials-gone-its-own-github-actions-deploy-still-needs-one-1gj6</link>
      <guid>https://dev.to/leobaniak/vercel-wants-static-credentials-gone-its-own-github-actions-deploy-still-needs-one-1gj6</guid>
      <description>&lt;p&gt;Count the long-lived tokens your deploy job can read right now. One is plenty, and a recent npm worm that scraped tokens straight out of CI pipelines made that point the expensive way. Vercel has now told its Marketplace vendors to get rid of theirs: they have until January 2027 to drop long-lived API keys or risk being delisted. Then developers opened Vercel's own GitHub Actions deployment guide.&lt;/p&gt;

&lt;h2&gt;
  
  
  The policy, and the stick behind it
&lt;/h2&gt;

&lt;p&gt;Vercel CTO Malte Ubl says the company surveyed more than 1,000 SaaS vendors and found that only 8.4% supported what Vercel considers secure authentication methods. Vercel's proposed replacement is OpenID Connect (OIDC) plus short-lived, identity-based tokens, instead of API keys that can stay valid indefinitely. CEO Guillermo Rauch put it on X as "time to kill 'static credentials'", arguing that stolen credentials should behave more like "self-destructing keys" that expire before an attacker gets much use out of them.&lt;/p&gt;

&lt;p&gt;There is a nearer deadline too. By November 30, Marketplace integrations that fall short of Vercel's authentication requirements get a "Not secure" warning on their listing.&lt;/p&gt;

&lt;p&gt;Good policy, honestly. A platform using its distribution to force vendors off static keys will move more of them than a hundred conference talks. (Delisting tends to get a product manager's attention faster than a threat model does.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Meanwhile, in the deploy guide
&lt;/h2&gt;

&lt;p&gt;Vercel's GitHub Actions deployment guide still tells you to authenticate deployments with a &lt;code&gt;VERCEL_TOKEN&lt;/code&gt; secret. Yordis Prieto, a senior software engineer at Fanatics, replied to the announcement that Vercel had "invented" OIDC federation yet still left him leaning on "a long-lived access token" in his CI pipeline. He also took aim at Vercel's non-expiring token option.&lt;/p&gt;

&lt;p&gt;Vercel's answer is in Ubl's announcement: the company is extending OIDC support across its API and CLI to retire long-lived tokens from deployment workflows. It also added GitHub OIDC authentication for its Container Registry in late September. Neither change removes &lt;code&gt;VERCEL_TOKEN&lt;/code&gt; from the documented GitHub Actions deploy flow today.&lt;/p&gt;

&lt;p&gt;Hypocrisy? That's a cheap shot. Retrofitting OIDC into an existing API and CLI is the exact job Vercel is handing its vendors, and it isn't a weekend of work. Still, you don't get to sell the cure while your own install docs keep handing out the disease.&lt;/p&gt;

&lt;h2&gt;
  
  
  What federation does that a secret can't
&lt;/h2&gt;

&lt;p&gt;If you haven't wired OIDC into a deploy job before, the general mechanism looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The CI job asks its platform's OIDC provider for a signed token. In GitHub Actions that requires &lt;code&gt;permissions: id-token: write&lt;/code&gt; on the job.&lt;/li&gt;
&lt;li&gt;The token carries claims about where the job runs: repository, ref, workflow, environment.&lt;/li&gt;
&lt;li&gt;The target service checks the signature against the issuer's public keys, checks the audience, and matches the claims against a trust policy you wrote.&lt;/li&gt;
&lt;li&gt;If everything matches, the service issues its own short-lived credential. It expires on its own, and nothing sits in your repository settings waiting to leak.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So the trust boundary moves. The question used to be "who can read this secret?" and becomes "who can get a workflow to run that matches this policy?" That's a better question, but it doesn't answer itself. Write a subject match loose enough to accept any branch or any repo in the org, and anyone who can push a workflow file can mint deploy credentials. Same blast radius, fresher paperwork. Pin the trust policy to one repository and one protected branch or environment. (We covered a similar fine-print problem when &lt;a href="https://dev.to/npm-trusted-publishing-dist-tags-oidc/"&gt;npm trusted publishing gained dist-tag support&lt;/a&gt;.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Living with VERCEL_TOKEN until the CLI catches up
&lt;/h2&gt;

&lt;p&gt;You can't federate a deploy that only takes a token. You can make the token less attractive to steal:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Give it an expiry. Skip the non-expiring option Prieto complained about.&lt;/li&gt;
&lt;li&gt;Put it in a GitHub environment with protection rules, and reference that environment only from the deploy job.&lt;/li&gt;
&lt;li&gt;Keep it out of any job that runs on pull requests from forks.&lt;/li&gt;
&lt;li&gt;Rotate it on a schedule, and immediately after anything that smells like a leak.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;production&lt;/span&gt;   &lt;span class="c1"&gt;# VERCEL_TOKEN lives here, behind required reviewers&lt;/span&gt;
    &lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@&amp;lt;full-40-char-sha&amp;gt;&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npx vercel deploy --prod --token="$VERCEL_TOKEN"&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;VERCEL_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.VERCEL_TOKEN }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The fine print nobody published
&lt;/h2&gt;

&lt;p&gt;Vercel hasn't published the survey methodology, named the companies it surveyed, or said how many existing Marketplace partners will have to change their authentication. Keep that in mind before you quote 8.4% at your vendor-review meeting.&lt;/p&gt;

&lt;p&gt;Then there's Vercel Connect, which lets applications and agents request scoped credentials at runtime using their deployment identity and handles token exchange and renewal. It still supports static API keys for providers that lack OAuth or another identity-based option. Its documentation describes storing those keys centrally and pulling them at runtime, and the keys underneath can still live indefinitely. A static key in a central vault is still a static key. It just has a nicer address.&lt;/p&gt;

&lt;p&gt;Until the CLI speaks OIDC, the most dangerous string in your Vercel deploy is the one Vercel's guide tells you to paste in. Vercel just gave its vendors an expiry date. Give your token one too.&lt;/p&gt;

</description>
      <category>oidc</category>
      <category>secrets</category>
      <category>vercel</category>
      <category>githubactions</category>
    </item>
    <item>
      <title>Safari Technology Preview 255 puts a button in select and spends fifteen fixes on line-clamp</title>
      <dc:creator>Leo</dc:creator>
      <pubDate>Fri, 23 Oct 2026 16:07:55 +0000</pubDate>
      <link>https://dev.to/leobaniak/safari-technology-preview-255-puts-a-button-in-select-and-spends-fifteen-fixes-on-line-clamp-264l</link>
      <guid>https://dev.to/leobaniak/safari-technology-preview-255-puts-a-button-in-select-and-spends-fifteen-fixes-on-line-clamp-264l</guid>
      <description>&lt;p&gt;Truncating text to a few lines has been copy-paste folklore for about as long as cards have had titles. You grab the snippet, it works on your test string, and you never look at it again until a float shows up below the cut and the whole thing falls apart. So yes, Safari Technology Preview 255 has a shiny forms feature. I'll get to it. The part I keep rereading is the fifteen separate &lt;code&gt;line-clamp&lt;/code&gt; fixes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The build, in one paragraph
&lt;/h2&gt;

&lt;p&gt;STP 255 shipped on October 22 for macOS Golden Gate and macOS Tahoe, covering WebKit changes 322011@main through &lt;a href="mailto:323397@main"&gt;323397@main&lt;/a&gt;. The new features are support for &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; in the native appearance of &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt;, the &lt;code&gt;side&lt;/code&gt; attribute on the SVG &lt;code&gt;&amp;lt;textPath&amp;gt;&lt;/code&gt; element, the inline layout engine switched on for SVG text, the &lt;code&gt;cross-origin-isolated&lt;/code&gt; Permissions Policy feature, transferring streams to an &lt;code&gt;AudioWorklet&lt;/code&gt;, and a CSS keyframe animation editor in Web Inspector. One method is gone: the unimplemented &lt;code&gt;document.implementation.createCSSStyleSheet()&lt;/code&gt;. The rest of the notes are mostly fixes, and there are a lot of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  A button inside the select
&lt;/h2&gt;

&lt;p&gt;The note is a single line: added support for &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; in the native appearance of &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt;. It ships with no markup example, so I'm not going to make one up and pretend it's official. What I can tell you is that the Forms section brings two related fixes. &lt;code&gt;:open&lt;/code&gt; had stopped matching a &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt; while its base appearance picker was still showing, and a &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt; was computing its selected option's style from outdated ancestor styles. Both are fixed. If you're building a customizable select in this preview and your &lt;code&gt;:open&lt;/code&gt; styles dropped while the picker was still on screen, that was this bug.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fifteen ways a clamp can leak
&lt;/h2&gt;

&lt;p&gt;Here's the pile, sorted by what actually went wrong:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Content past the clamp point stayed around. Block boxes after it stayed visible and kept taking up space. Floats after it stayed visible, and so did absolutely positioned elements whose containing block is an inline after the clamp point.&lt;/li&gt;
&lt;li&gt;Floats misbehaved. They made the &lt;code&gt;line-clamp&lt;/code&gt; container taller, added to its scrollable overflow, and painted past its block-end content edge.&lt;/li&gt;
&lt;li&gt;The ellipsis landed in the wrong place. It could appear after a &lt;code&gt;&amp;lt;wbr&amp;gt;&lt;/code&gt; in a &lt;code&gt;white-space: nowrap&lt;/code&gt; line, on a line containing only a float, or refuse to hang with &lt;code&gt;hanging-punctuation&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;line-clamp: auto&lt;/code&gt; had its own problems. It couldn't clamp between sibling blocks, it clamped inside a box with a set height, and it applied no clamp at all until the clamp point was found.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The other two: a &lt;code&gt;line-clamp&lt;/code&gt; container now establishes an independent formatting context, and content before the clamp point no longer loses its lines when it hasn't changed.&lt;/p&gt;

&lt;p&gt;That's a whole category of layout bugs getting worked through in one build (a clamp that leaks floats is not a clamp). If you wrote off &lt;code&gt;line-clamp&lt;/code&gt; in this engine after a float broke it, test it again in this build.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cascade fixes you'll feel in hand-written CSS
&lt;/h2&gt;

&lt;p&gt;This one is small and it bugs me that it existed. &lt;code&gt;font-family: initial&lt;/code&gt; was behaving like &lt;code&gt;font-family: inherit&lt;/code&gt;, so you got the parent's font instead of the property's initial value. Fixed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.plain-text&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;initial&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;initial&lt;/code&gt; and &lt;code&gt;inherit&lt;/code&gt; are two different keywords that mean two different things, and a reset rule written with one was quietly getting the other.&lt;/p&gt;

&lt;p&gt;The deeper cascade fixes: &lt;code&gt;revert&lt;/code&gt; inside &lt;code&gt;@starting-style&lt;/code&gt; was dropping &lt;code&gt;@starting-style&lt;/code&gt; declarations from lower cascade layers. &lt;code&gt;revert&lt;/code&gt; and &lt;code&gt;revert-layer&lt;/code&gt; used the wrong cascade origin for visited and unvisited link styles, which wrongly disabled native appearance. &lt;code&gt;:visited&lt;/code&gt; wasn't updating for links inside shadow trees. &lt;code&gt;@scope&lt;/code&gt; now invalidates scope membership correctly when structural DOM changes affect the scoping root selector. And tree-scoped &lt;code&gt;var()&lt;/code&gt; and custom function references inside an &lt;code&gt;@function&lt;/code&gt; now resolve when the function is called from a &lt;code&gt;:host&lt;/code&gt; rule. If you run layers, entry animations or shadow DOM in this preview, those are your bugs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two accessibility fixes for HTML you already write
&lt;/h2&gt;

&lt;p&gt;Radio groups first. In HTML, putting &lt;code&gt;required&lt;/code&gt; on any radio in a group makes the whole group required. The notes say WebKit now also exposes every radio in the group as required to assistive tech when only one carries the attribute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;fieldset&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;legend&amp;gt;&lt;/span&gt;Shipping speed&lt;span class="nt"&gt;&amp;lt;/legend&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;label&amp;gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"radio"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"speed"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"standard"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt; Standard&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;label&amp;gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"radio"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"speed"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"express"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt; Express&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/fieldset&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Second, VoiceOver presses were dispatching a &lt;code&gt;click&lt;/code&gt; event with an &lt;code&gt;event.detail&lt;/code&gt; of 0. That's fixed too, and it's a good excuse to stop guessing the input device from &lt;code&gt;event.detail&lt;/code&gt;. A click is a click.&lt;/p&gt;

&lt;p&gt;There's also a popover fix in the HTML section: tabbing to a popover invoker now moves focus into the popover itself when the popover is focusable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it stands
&lt;/h2&gt;

&lt;p&gt;This is a preview build of one engine. The notes make no Baseline claim and I won't add one, so the &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt; button support and the &lt;code&gt;side&lt;/code&gt; attribute are things to try out on a test page, not to ship. One known issue to keep in mind: &lt;code&gt;document.activeElement&lt;/code&gt; returns the &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; element instead of &lt;code&gt;null&lt;/code&gt; in HTML documents with no &lt;code&gt;body&lt;/code&gt;. The notes say other browsers behave differently and that this breaks the WebDriver Get Active Element command.&lt;/p&gt;

&lt;p&gt;It's like a roguelike balance patch where the headline is a new item and the actual changes are fifteen fixes to the build you were already running. A solid 7 for features and a 9 for the cleanup.&lt;/p&gt;

</description>
      <category>safari</category>
      <category>webkit</category>
      <category>stp</category>
      <category>browserrelease</category>
    </item>
    <item>
      <title>GitHub's package firewall puts a policy check between your runners and npm</title>
      <dc:creator>Leo</dc:creator>
      <pubDate>Thu, 22 Oct 2026 10:24:10 +0000</pubDate>
      <link>https://dev.to/leobaniak/githubs-package-firewall-puts-a-policy-check-between-your-runners-and-npm-3njf</link>
      <guid>https://dev.to/leobaniak/githubs-package-firewall-puts-a-policy-check-between-your-runners-and-npm-3njf</guid>
      <description>&lt;p&gt;I used to treat &lt;code&gt;npm ci&lt;/code&gt; as the boring part of a job log, the stretch where you refill your coffee. Lately I read it differently, because that stretch is when a runner pulls code nobody on the team has looked at and runs its install scripts while the job's tokens are still loaded. GitHub's new package firewall is aimed at exactly that stretch.&lt;/p&gt;

&lt;p&gt;GitHub put the firewall into public preview on October 20 for all GitHub Enterprise Cloud customers, including Enterprise Cloud with data residency. GitHub's reasoning will sound familiar to anyone who owns a pipeline: agents and CI runners install dependencies to finish a task or a build with no human in the loop, which makes them the easy target for malware that fires at install time.&lt;/p&gt;

&lt;h2&gt;
  
  
  A gate in the network path
&lt;/h2&gt;

&lt;p&gt;The firewall sits between your endpoints (or your internal registries) and the upstream package repositories. Both metadata requests for package lists and the package downloads themselves flow through it, and it checks each one against policy before the install proceeds.&lt;/p&gt;

&lt;p&gt;Administrators set that policy at the enterprise level. The controls available today are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a configurable cooldown period&lt;/li&gt;
&lt;li&gt;checks against the GitHub Advisory Database for malware or vulnerability advisories&lt;/li&gt;
&lt;li&gt;supply-chain checks such as attestation downgrades&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the firewall blocks a package by mistake, the user can request an exception through a workflow similar to other GitHub Advanced Security features. The service is free during the preview. At general availability GitHub plans to charge for it on metered usage, similar to Actions billing.&lt;/p&gt;

&lt;p&gt;Right now it only supports npm.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the cooldown is the control I'd turn on first
&lt;/h2&gt;

&lt;p&gt;I find the cooldown the most interesting control, because of how these attacks usually play out. A malicious version gets published, someone spots it, and the registry pulls it. A cooldown keeps brand-new versions away from your builds for a period you choose, so your runner doesn't have to be among the first machines to install them. You stop being an early adopter by default.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pointing a runner at it
&lt;/h2&gt;

&lt;p&gt;GitHub sends you to its docs page, &lt;a href="https://docs.github.com/enterprise-cloud@latest/code-security/how-tos/secure-your-supply-chain/secure-your-dependencies/protecting-endpoints-with-package-firewall" rel="noopener noreferrer"&gt;Protecting endpoints with package firewall&lt;/a&gt;, for setup. The changelog doesn't give the endpoint format or the auth method, so take both from the docs. For npm, the change on the runner side comes down to a registry setting, roughly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# values come from the setup docs; these are placeholders&lt;/span&gt;
npm config &lt;span class="nb"&gt;set &lt;/span&gt;registry &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PACKAGE_FIREWALL_URL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
npm ci
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before you trust it, check that installs really go through the firewall. Scoped registry settings in a committed &lt;code&gt;.npmrc&lt;/code&gt; and the &lt;code&gt;resolved&lt;/code&gt; URLs in a lockfile can both point npm somewhere you didn't expect. And if the runner can still reach the public registry directly, you have a gate with a gap beside it, so restrict egress where you can.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it gets awkward
&lt;/h2&gt;

&lt;p&gt;A few things gave me pause:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;npm only. A polyglot repo with pip or Maven jobs is still installing those packages unchecked.&lt;/li&gt;
&lt;li&gt;A cooldown cuts both ways. If a dependency ships an urgent fix today, a long cooldown keeps that fix away from your builds too. Decide ahead of time how you'll handle that exception.&lt;/li&gt;
&lt;li&gt;A false positive now means a red build that waits on someone approving an exception. Find out who that is before the first Friday afternoon block.&lt;/li&gt;
&lt;li&gt;It's a preview, and GitHub says it is still scaling up the infrastructure behind it. Every install depends on it once you route through it, so watch your install times.&lt;/li&gt;
&lt;li&gt;Metered billing at GA means install volume becomes a cost line. Busy monorepos with lots of matrix jobs should get an estimate early.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Same idea, different layers
&lt;/h2&gt;

&lt;p&gt;GitLab put its &lt;a href="https://dev.to/gitlab-dependency-firewall-pre-install-gate/"&gt;Dependency Firewall into early access&lt;/a&gt; for Premium and Ultimate. It works with GitLab's own registry and with external Nexus and Artifactory registries, and it covers npm, pip, Poetry, Maven, Gradle and Bundler. So it supports more ecosystems today, if you're on GitLab.&lt;/p&gt;

&lt;p&gt;Teams already running a repository manager can apply similar policy at the proxy itself. Sonatype and JFrog both sell this kind of control for Nexus and Artifactory.&lt;/p&gt;

&lt;p&gt;Closer to the client, pnpm (&lt;code&gt;minimumReleaseAge&lt;/code&gt;) and Yarn (&lt;code&gt;npmMinimalAgeGate&lt;/code&gt;) can hold back fresh versions on their own, with no proxy involved. Dependabot and Renovate have cooldown settings for update PRs as well. Those work one step earlier: they slow down the PRs that bump a version, but they don't stop a job from installing whatever a loose version range resolves to.&lt;/p&gt;

&lt;p&gt;GitHub's version stands out mainly for where it sits: one enterprise-wide policy in front of every runner and coding agent you route through it. If you're already on Enterprise Cloud and mostly install npm packages, it costs you a registry setting to try during the free preview.&lt;/p&gt;

&lt;p&gt;Next I'm watching for a second ecosystem. pip is the one I'd want, because a lot of my CI time goes to Python test jobs and none of them get this protection yet. If you've routed a busy pipeline through the preview, I'd like to hear what it did to your install times.&lt;/p&gt;

</description>
      <category>supplychain</category>
      <category>packagesecurity</category>
      <category>github</category>
      <category>cirunners</category>
    </item>
    <item>
      <title>The Interop 2027 CSS shortlist reads like a list of hacks you get to delete</title>
      <dc:creator>Leo</dc:creator>
      <pubDate>Wed, 21 Oct 2026 16:08:17 +0000</pubDate>
      <link>https://dev.to/leobaniak/the-interop-2027-css-shortlist-reads-like-a-list-of-hacks-you-get-to-delete-5ajk</link>
      <guid>https://dev.to/leobaniak/the-interop-2027-css-shortlist-reads-like-a-list-of-hacks-you-get-to-delete-5ajk</guid>
      <description>&lt;p&gt;You want hairlines between the cells of a 3×3 grid. Two cells have content. Seven are empty. And an empty grid cell has no element, so there is nothing to put &lt;code&gt;border: 1px solid&lt;/code&gt; on.&lt;/p&gt;

&lt;p&gt;So you fake it. Paint the grid container red, set &lt;code&gt;gap: 1px&lt;/code&gt;, give every child a white background, and pad the markup with seven empty divs so the background only shows through the gaps. It works. It's also seven DOM nodes that exist purely to hide paint.&lt;/p&gt;

&lt;p&gt;Josh W. Comeau shows that exact workaround in his new post on Interop 2027, and then shows the property that makes it unnecessary. That's the useful way to read his whole list. Most of the ten features he voted for replace something we currently fake.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the list gets made
&lt;/h2&gt;

&lt;p&gt;Interop is the yearly project where the browser vendors (Comeau names Google, Apple, Microsoft and Mozilla, along with Igalia and Bocoup) agree on a set of focus areas and work on them together. He writes that a focus area added to Interop often reaches near-100% support across the major browsers within a year.&lt;/p&gt;

&lt;p&gt;His example is subgrid. Firefox implemented it in 2019, other browsers barely moved for years, then it went into Interop 2023 and was widely available across modern browsers by the end of that year.&lt;/p&gt;

&lt;p&gt;For 2027, the team at Mozilla built a ranking system where developers upvote the features they want, and Comeau writes that they plan to use that ranking data to decide what goes into next year's Interop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lines that belong to the grid
&lt;/h2&gt;

&lt;p&gt;Gap decorations add a border-like syntax to the grid itself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.grid&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;grid-template-columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;repeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;grid-template-rows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;repeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="no"&gt;red&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="no"&gt;red&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;rule&lt;/code&gt; is a shorthand, like &lt;code&gt;border&lt;/code&gt;. Width, style, color. Use &lt;code&gt;row-rule&lt;/code&gt; or &lt;code&gt;column-rule&lt;/code&gt; when you only want lines in one direction.&lt;/p&gt;

&lt;p&gt;Comeau lists three problems with the background trick: the empty nodes, the fact that it only works on a solid page background, and the lost flexibility. For example, you can't leave space between the grid's outer border and its inner lines. The mechanism explains all three. The lines come from paint showing through a gap, so the gap is all you get to style. &lt;code&gt;rule&lt;/code&gt; makes the line its own thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Corners the border box follows
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;border-radius&lt;/code&gt; puts a quarter-circle on each corner. &lt;code&gt;corner-shape&lt;/code&gt; swaps that curve for something else: &lt;code&gt;squircle&lt;/code&gt;, &lt;code&gt;bevel&lt;/code&gt;, &lt;code&gt;notch&lt;/code&gt;, &lt;code&gt;scoop&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.box&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;16px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;corner-shape&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;bevel&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You could already cut shapes like these with &lt;code&gt;clip-path&lt;/code&gt; or masks. The difference is where the change happens. According to Comeau, &lt;code&gt;corner-shape&lt;/code&gt; changes the shape of the border box itself, so borders, outlines and box-shadows follow the new corner. A clip only hides pixels, so the border and shadow get cut off with everything else instead of tracing the new edge.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;border-shape&lt;/code&gt; goes a level lower. You draw the border yourself out of straight lines, arcs and Bézier curves, and the same thing holds: the real border box changes, so outlines and shadows trace it. Comeau calls &lt;code&gt;corner-shape&lt;/code&gt; a perfect progressive enhancement. Unsupported browsers fall back to the plain rounded corner, which is fine.&lt;/p&gt;

&lt;h2&gt;
  
  
  auto gets a transition
&lt;/h2&gt;

&lt;p&gt;You can't animate to &lt;code&gt;height: auto&lt;/code&gt;. You've hit this. &lt;code&gt;interpolate-size&lt;/code&gt; fixes it, alongside the related &lt;code&gt;calc-size()&lt;/code&gt; function. Comeau's accordion demo opts in at the root and animates the &lt;code&gt;::details-content&lt;/code&gt; pseudo-element:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;html&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;interpolate-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;allow-keywords&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;@supports&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;interpolate-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;allow-keywords&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nt"&gt;details&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;overflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="err"&gt;&amp;amp;::details-content&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;height&lt;/span&gt; &lt;span class="m"&gt;300ms&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;content-visibility&lt;/span&gt; &lt;span class="m"&gt;300ms&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="py"&gt;transition-behavior&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;allow-discrete&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nd"&gt;:open::details-content&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep that &lt;code&gt;@supports&lt;/code&gt; gate. Comeau warns that without it, unsupported browsers get a brief delay added. He also notes this has been in Chrome since 2024, which is a long time to wait for an accordion.&lt;/p&gt;

&lt;h2&gt;
  
  
  State you used to read from JavaScript
&lt;/h2&gt;

&lt;p&gt;Several picks are queries for state that CSS couldn't see before.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scroll-state container queries. Put &lt;code&gt;container-type: scroll-state&lt;/code&gt; on a sticky header and &lt;code&gt;@container scroll-state(stuck: top)&lt;/code&gt; matches once it sticks. The same API answers &lt;code&gt;scrollable: top&lt;/code&gt; and &lt;code&gt;scrollable: bottom&lt;/code&gt;, which is all you need to fade those scroll-hint gradients in and out.&lt;/li&gt;
&lt;li&gt;Anchored container queries, from Level 2 of Anchor Positioning. They let you check whether a positioned element is using a fallback position, so a tooltip's pointer can flip when the tooltip does.&lt;/li&gt;
&lt;li&gt;Scroll-triggered animations. These are a separate proposal from scroll-driven ones: the animation starts when the user scrolls to a point and then runs over a specified duration. Today that means &lt;code&gt;IntersectionObserver&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;reading-flow&lt;/code&gt; and &lt;code&gt;reading-order&lt;/code&gt;. Tab order follows the DOM, not the layout, so a grid that rearranges on mobile focuses things in the wrong order. &lt;code&gt;reading-flow: grid-rows&lt;/code&gt; on the grid makes focus follow the visual rows.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Functions, and one that exists nowhere
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;@function&lt;/code&gt; lets you define a custom function, such as &lt;code&gt;--gradient-to-transparent(--color, --direction: bottom)&lt;/code&gt;, and return its value with &lt;code&gt;result&lt;/code&gt;. Call it like any other value: &lt;code&gt;background: --gradient-to-transparent(red)&lt;/code&gt;. Comeau writes that it is already implemented in Chromium browsers.&lt;/p&gt;

&lt;p&gt;Then there's &lt;code&gt;inherit()&lt;/code&gt;. Comeau says he couldn't find it implemented in any browser. It takes the inherited value and lets you do math on it, which is exactly what nested radii need:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;inherit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;-&lt;/span&gt; &lt;span class="m"&gt;4px&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;He'd be a bit surprised if it makes the 2027 cut. I'd vote for it anyway.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where support stands
&lt;/h2&gt;

&lt;p&gt;The support tables in Comeau's post, marked current as of October 2026, list Chrome 155 and Edge 154 as supporting each feature that has a table, and Firefox 157 and Safari 27 as not supporting it. &lt;code&gt;inherit()&lt;/code&gt; has no table because it isn't listed on caniuse. That's one engine family carrying every tabled feature today, which is the gap Interop exists to close.&lt;/p&gt;

&lt;p&gt;For your next layout, sort by fallback. &lt;code&gt;corner-shape&lt;/code&gt; and the gated &lt;code&gt;interpolate-size&lt;/code&gt; accordion degrade to something perfectly usable, so ship them now. Gap decorations are harder. The fallback is no lines at all, so the background trick stays until &lt;code&gt;rule&lt;/code&gt; ships in more than one engine. Whether that happens in 2027 depends partly on how the Mozilla ranking comes out.&lt;/p&gt;

</description>
      <category>css</category>
      <category>interop2027</category>
      <category>browsersupport</category>
      <category>gapdecorations</category>
    </item>
    <item>
      <title>CSS random() puts a little deliberate mess back into your layout</title>
      <dc:creator>Leo</dc:creator>
      <pubDate>Tue, 20 Oct 2026 16:08:13 +0000</pubDate>
      <link>https://dev.to/leobaniak/css-random-puts-a-little-deliberate-mess-back-into-your-layout-43on</link>
      <guid>https://dev.to/leobaniak/css-random-puts-a-little-deliberate-mess-back-into-your-layout-43on</guid>
      <description>&lt;p&gt;If your "hand-made" look is a Sass loop spitting out forty &lt;code&gt;:nth-child&lt;/code&gt; rules with hard-coded rotations, I've got bad news. That was never randomness. It was a lookup table in a fake mustache. (Everybody shipped one. Mine had a diff you could see from orbit.)&lt;/p&gt;

&lt;p&gt;Brecht De Ruyte's new piece in Smashing Magazine is about what replaces it: the native &lt;code&gt;random()&lt;/code&gt; function from the CSS Values and Units Level 5 spec. According to his article, Safari landed it first, Chrome 156 is rolling it out right now, and Mozilla has sent positive signals about Firefox. The syntax isn't actually the best part. De Ruyte builds design systems for a living and says so up front ("Consistency is usually our bread and butter as design engineers"), and then he goes looking for the few places where a bit of mess is the right call.&lt;/p&gt;

&lt;h2&gt;
  
  
  The arguments, one at a time
&lt;/h2&gt;

&lt;p&gt;Here's the grammar he quotes from the spec:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;random( &amp;lt;random-caching-key&amp;gt;?, &amp;lt;min&amp;gt;, &amp;lt;max&amp;gt; [, by &amp;lt;step&amp;gt;]? )
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In practice he writes plain comma-separated arguments:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;rotate&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;random&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;-3deg&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="err"&gt;3&lt;/span&gt;&lt;span class="nt"&gt;deg&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nt"&gt;inline-size&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;random&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;20&lt;/span&gt;&lt;span class="nt"&gt;px&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="err"&gt;80&lt;/span&gt;&lt;span class="nt"&gt;px&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="err"&gt;10&lt;/span&gt;&lt;span class="nt"&gt;px&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nt"&gt;animation-duration&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;random&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;5&lt;/span&gt;&lt;span class="nt"&gt;s&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="err"&gt;3&lt;/span&gt;&lt;span class="nt"&gt;s&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;2&lt;/span&gt;&lt;span class="nt"&gt;s&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The min and max have to share the exact same unit type. &lt;code&gt;random(-4deg, 4deg)&lt;/code&gt; is fine, and so is the unitless &lt;code&gt;random(0.3, 1)&lt;/code&gt;. &lt;code&gt;random(2rem, 100px)&lt;/code&gt; is invalid. If you need to mix units, De Ruyte says to put &lt;code&gt;calc()&lt;/code&gt; inside the arguments.&lt;/p&gt;

&lt;p&gt;The third argument is the step. Without it the browser picks from a continuous range. With it, the value rounds to discrete increments, so &lt;code&gt;top: random(0px, 60px, 15px)&lt;/code&gt; only ever lands on 0, 15, 30, 45 or 60px. That's jitter that still respects your grid.&lt;/p&gt;

&lt;p&gt;Then there's the caching key, the one piece that needs a real explanation. By default, every &lt;code&gt;random()&lt;/code&gt; on a property rolls its own independent value. Randomize &lt;code&gt;inline-size&lt;/code&gt; and &lt;code&gt;block-size&lt;/code&gt; separately and your "random square" comes out as a random rectangle. Give both declarations the same key and the engine shares the computed value between them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.card-thumbnail&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;inline-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--square-size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;60px&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;120px&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;block-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--square-size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;60px&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;120px&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Quick tangent, because this tripped me up. &lt;code&gt;--square-size&lt;/code&gt; looks exactly like a custom property, but nothing in that snippet ever declares it. It doesn't hold a value. It's a name the two declarations share. OK, back on track.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wobbly type, bouncing letters, a fanned-out deck
&lt;/h2&gt;

&lt;p&gt;De Ruyte's first demo splits a word into &lt;code&gt;.char&lt;/code&gt; spans and gives each letter a tiny tilt, a nudge and a little ink variance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.char&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;inline-block&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;rotate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;-2.8deg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;2.8deg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;translate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;-1.5px&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1.5px&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;-2.8px&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;2.8px&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.02&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hover the word and a &lt;code&gt;.word:hover .char&lt;/code&gt; rule resets every letter to straight. Honestly, the first time I watched letterforms wobble with zero JavaScript involved, I felt the way I did the first time &lt;code&gt;:hover&lt;/code&gt; worked on something that wasn't a link. A small thing, and it made me grin.&lt;/p&gt;

&lt;p&gt;The second demo feeds &lt;code&gt;random()&lt;/code&gt; into custom properties (&lt;code&gt;--jump&lt;/code&gt;, &lt;code&gt;--dur&lt;/code&gt; and a negative &lt;code&gt;--delay&lt;/code&gt;), so every letter is already mid-bounce on page load and no two move in sync. The splayed-card demo is the one I'd steal. &lt;code&gt;sibling-index()&lt;/code&gt; and &lt;code&gt;sibling-count()&lt;/code&gt; compute the fan structure, &lt;code&gt;random()&lt;/code&gt; adds jitter on top, and &lt;code&gt;animation-timeline: scroll(root)&lt;/code&gt; drives it as you scroll. Math sets the layout, and randomness only nudges it. Then he closes on a punk fanzine called "Never mind the defaults", which, as someone whose teens were scored by drop-D riffs, I respect.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it stays out
&lt;/h2&gt;

&lt;p&gt;His no-go list is the part to print out. No randomness in core layouts and grids (column gutters, form field widths, container padding). None in body copy (font sizes, line heights, letter spacing on long paragraphs). And no tilted or shifted buttons and links unless they reset on hover and focus. In his words: "If a user has to chase a moving or crooked button, you've failed them."&lt;/p&gt;

&lt;p&gt;There's also the accessibility homework. Split a word into spans and screen readers may read it out letter by letter, so his markup puts &lt;code&gt;aria-label&lt;/code&gt; on the &lt;code&gt;h1&lt;/code&gt; and &lt;code&gt;aria-hidden="true"&lt;/code&gt; on the wrapper span. Under &lt;code&gt;prefers-reduced-motion: reduce&lt;/code&gt; he removes the animation and also flattens &lt;code&gt;rotate&lt;/code&gt; and &lt;code&gt;translate&lt;/code&gt;. His reasoning is that a lot of random angles can bother people while they scroll, not just the motion.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using it before every engine has it
&lt;/h2&gt;

&lt;p&gt;Because it's still rolling out, De Ruyte wraps the enhancement in &lt;code&gt;@supports&lt;/code&gt; and keeps a straight default:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.badge&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;rotate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0deg&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;@supports&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rotate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;-2deg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;2deg&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;.badge&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="py"&gt;rotate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;-2.5deg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;2.5deg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;He also points to &lt;code&gt;random-item()&lt;/code&gt;, which is in the same spec and picks from a list instead of a range (random design tokens, say). He notes that no browser supports it yet.&lt;/p&gt;

&lt;p&gt;Verdict: your tokens set the structure, and the engine adds a few degrees of controlled chaos. That's an 8/10, and it only drops two points because one engine still hasn't shipped it.&lt;/p&gt;

</description>
      <category>css</category>
      <category>random</category>
      <category>cssvalues5</category>
      <category>designsystems</category>
    </item>
    <item>
      <title>GitLab Secrets Manager hits GA in 19.5 and scopes credentials to the job</title>
      <dc:creator>Leo</dc:creator>
      <pubDate>Tue, 20 Oct 2026 10:24:22 +0000</pubDate>
      <link>https://dev.to/leobaniak/gitlab-secrets-manager-hits-ga-in-195-and-scopes-credentials-to-the-job-3ei8</link>
      <guid>https://dev.to/leobaniak/gitlab-secrets-manager-hits-ga-in-195-and-scopes-credentials-to-the-job-3ei8</guid>
      <description>&lt;p&gt;Open the CI/CD settings of any GitLab project older than two years and count the masked variables. (Go on. I'll wait.) Each one is a raw value sitting in a settings page, readable by whatever jobs the old protection flags allow, and good luck proving which job actually read it last Tuesday. GitLab now wants those variables retired. Secrets Manager is generally available in GitLab 19.5 for Premium and Ultimate, on GitLab.com and GitLab Self-Managed. GitLab Dedicated support is listed as coming soon.&lt;/p&gt;

&lt;p&gt;When we covered the &lt;a href="https://dev.to/gitlab-secrets-manager-eso-terraform-api/"&gt;public beta in August&lt;/a&gt;, it was a preview. Now it is a product, and it comes with a price tag.&lt;/p&gt;

&lt;h2&gt;
  
  
  What exactly shipped
&lt;/h2&gt;

&lt;p&gt;The store runs on OpenBao, the open-source fork of HashiCorp Vault, and GitLab says values are encrypted at rest with AES-256-GCM. It is a paid add-on billed through GitLab Credits: one credit per secret stored per month, and one credit per 2,500 read operations.&lt;/p&gt;

&lt;p&gt;The CI side works like this, per GitLab's announcement:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A job declares the secrets it needs with the &lt;code&gt;secrets:&lt;/code&gt; keyword in &lt;code&gt;.gitlab-ci.yml&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;GitLab injects a value only into jobs that declare it &lt;strong&gt;and&lt;/strong&gt; fall inside its scope. Scope conditions take wildcards on branch and environment, and they combine, so one rule can demand both a protected branch and a &lt;code&gt;production/*&lt;/code&gt; environment.&lt;/li&gt;
&lt;li&gt;The job gets a path to a temporary file instead of the value itself. Values are masked in logs and discarded when the job finishes.&lt;/li&gt;
&lt;li&gt;Jobs fetch from OpenBao with short-lived tokens bound to that job's identity.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where the trust boundary moved
&lt;/h2&gt;

&lt;p&gt;Isn't this just Vault with a tanuki on it? Partly, and GitLab doesn't hide the OpenBao underneath. The difference is who decides access. Secrets Manager reuses GitLab's namespace isolation: projects inherit secrets from their parent groups, and owners grant read, write and delete through the membership model you already maintain. There is no second permission model to drift out of sync with the first one (the usual way a "temporary" Vault policy turns permanent).&lt;/p&gt;

&lt;p&gt;Then the audit trail. GitLab streams every create, read, update and delete event to your audit event destinations, and every CI/CD read carries the pipeline and job ID, tied back to the commit, the user and the merge request. When a token shows up somewhere it shouldn't, you can work backwards from the read event to the MR that triggered it. A plain CI/CD variable never gave you that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Moving a pipeline over
&lt;/h2&gt;

&lt;p&gt;The path today is manual. Create the secret at the group or project level, give it the narrowest branch and environment scope you can live with, then add a &lt;code&gt;secrets:&lt;/code&gt; block to the jobs that need it. Since the job receives a file path, check every script and tool that expected the raw value in an environment variable. That's where the first migration bug usually turns up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Read this before you migrate
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;No migration tooling yet. GitLab says a migration workflow for CI/CD variables is in development. Until it lands, every variable you move is a manual change and a chance to fat-finger a scope rule.&lt;/li&gt;
&lt;li&gt;The runtime side is not GA. The consumers beyond CI (External Secrets Operator for Kubernetes, Terraform and OpenTofu data sources, the OpenBao and Vault CLIs over a Vault-compatible API, and the Secrets Manager API) are still listed as beta or in development.&lt;/li&gt;
&lt;li&gt;Reads are metered. Billing per 2,500 reads is cheap until a wide matrix build fetches the same credential in every cell. Scope tightly and the bill shrinks along with the blast radius.&lt;/li&gt;
&lt;li&gt;Masking does not stop exfiltration. A job that is allowed to read the file can still send it anywhere it has network access. The scope rule is the real control. The log mask is cosmetic.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How other CI platforms divide up secrets
&lt;/h2&gt;

&lt;p&gt;Every CI platform has some answer to "which job gets which credential". They draw the boundary in different places:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Actions&lt;/strong&gt; has repository, organization and environment secrets. Environment protection rules (required reviewers, deployment branch rules) decide which runs can see an environment's secrets, and OIDC federation to cloud providers lets you skip storing long-lived cloud keys at all.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Jenkins&lt;/strong&gt; uses the Credentials plugin with folder-scoped credentials and &lt;code&gt;withCredentials&lt;/code&gt; bindings. It is very flexible. Tracing which build used which credential is left to whatever plugins and logging you add yourself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CircleCI&lt;/strong&gt; groups secrets into contexts that can be restricted to security groups, and it issues OIDC tokens to jobs for cloud federation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A standalone Vault or OpenBao cluster&lt;/strong&gt; with JWT auth against your CI's identity tokens is the better fit when several CI systems, or non-CI workloads, share the same credentials. You get one store with many consumers, and it doesn't care which CI vendor you leave next year.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Buddy&lt;/strong&gt; lets you define variables at the workspace, project, pipeline or single-action level and &lt;a href="https://buddy.works/docs/pipelines/variables-and-encryption?utm_source=cicd.deployment.to&amp;amp;utm_medium=referral&amp;amp;utm_campaign=gitlab-secrets-manager-ga" rel="noopener noreferrer"&gt;mark them as encrypted&lt;/a&gt; so they are masked in logs. That suits teams that use Buddy as the delivery layer across more than one Git host and want a deploy credential visible to one action instead of the whole pipeline.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To be fair to GitLab, if your repositories, runners and permissions already live there, its native manager is the better fit. Reusing the permission model you already have is the entire selling point, and none of the alternatives above can offer that.&lt;/p&gt;

&lt;p&gt;My verdict: worth adopting for any job that touches production, once you have budgeted the reads and accepted a manual migration. Just remember what the feature promises. It decides which jobs get the secret. Whatever that job does with it afterwards is still your problem.&lt;/p&gt;

</description>
      <category>gitlab</category>
      <category>secretsmanagement</category>
      <category>openbao</category>
      <category>cicdsecurity</category>
    </item>
    <item>
      <title>GitHub Actions lockfiles pin the commit and leave your tags alone</title>
      <dc:creator>Leo</dc:creator>
      <pubDate>Sat, 17 Oct 2026 10:24:15 +0000</pubDate>
      <link>https://dev.to/leobaniak/github-actions-lockfiles-pin-the-commit-and-leave-your-tags-alone-1i2c</link>
      <guid>https://dev.to/leobaniak/github-actions-lockfiles-pin-the-commit-and-leave-your-tags-alone-1i2c</guid>
      <description>&lt;p&gt;You reviewed the action. You approved the pull request. Then someone moved the tag, and your next run executed code nobody on your team has ever read. (Tags are sticky notes. Nobody ever promised they were signatures.) GitHub's answer is workflow dependency lockfiles, now in public preview. You keep &lt;code&gt;@v1&lt;/code&gt; in your YAML, and the run uses the commit you actually reviewed.&lt;/p&gt;

&lt;h2&gt;
  
  
  What shipped
&lt;/h2&gt;

&lt;p&gt;The preview covers GitHub Enterprise Cloud, Enterprise Cloud with data residency, Team and Free. GitHub Enterprise Server is not on that list.&lt;/p&gt;

&lt;p&gt;The work happens in a GitHub CLI extension, &lt;code&gt;gh actions-lock&lt;/code&gt;. It generates &lt;code&gt;.github/workflows/actions.lock&lt;/code&gt;, which records reviewable commits for each action, for that action's transitive dependencies, and for reusable workflow dependencies. You onboard workflows one at a time. Once a workflow is onboarded, every one of its dependencies must have a lockfile entry, and Actions runs the recorded commit even if the tag or branch has since moved. The extension can also check whether the lockfile is stale without modifying anything, and GitHub says that check can run in CI.&lt;/p&gt;

&lt;p&gt;Two pieces sit around it. A Require lockfile policy, also in preview, lets enterprise, organization and repository administrators make lockfiles mandatory within their scope. Dependabot, when it opens a version update for an action in an onboarded workflow, regenerates the matching lockfile entry in the same pull request.&lt;/p&gt;

&lt;h2&gt;
  
  
  Couldn't I just SHA-pin everything?
&lt;/h2&gt;

&lt;p&gt;You could. Plenty of us do. But anyone who has maintained it knows what it costs: 40 characters of hex on every &lt;code&gt;uses:&lt;/code&gt; line, a &lt;code&gt;# v1&lt;/code&gt; comment that quietly drifts out of sync with the SHA, and diffs nobody reads. Worse, your pin covers one edge of the graph. A composite action you pinned by SHA can still call another action by a floating tag, and you never see that line because it lives in someone else's repository.&lt;/p&gt;

&lt;p&gt;The lockfile closes both gaps. The readable reference stays in the workflow, the commit moves into a file you can diff, and the transitive dependencies get locked too. Same trick &lt;code&gt;package-lock.json&lt;/code&gt; and &lt;code&gt;go.sum&lt;/code&gt; pulled years ago, finally applied to the layer of the stack that holds your deploy credentials.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rolling it out
&lt;/h2&gt;

&lt;p&gt;Install the extension from the public &lt;code&gt;github/gh-actions-lock&lt;/code&gt; repository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gh extension &lt;span class="nb"&gt;install &lt;/span&gt;github/gh-actions-lock
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then generate the lockfile, review it, and commit it together with the workflow changes the tool produces. Start with the workflows that hold secrets or deploy permissions. Add the stale-lockfile check to CI so a drifted lockfile fails a pull request. Turn on Require lockfile only after the important workflows are onboarded, unless you enjoy a red Monday. The exact subcommands are in GitHub's workflow dependency lockfiles documentation, and I am not going to guess them for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a lockfile will not do
&lt;/h2&gt;

&lt;p&gt;GitHub says this plainly in the changelog: locking "does not determine whether the dependency itself is safe, so review dependencies before locking them." Lock a backdoored commit and you get a backdoored workflow that runs exactly the same way every time. Reproducible. Still compromised.&lt;/p&gt;

&lt;p&gt;The Dependabot integration deserves the same suspicion. When a bot PR updates both the reference and the locked commit, that PR becomes your trust boundary. Rubber-stamp it and you have automated the tag move with extra steps.&lt;/p&gt;

&lt;p&gt;Then there is scope. The changelog describes locking commits for actions, their transitive dependencies and reusable workflows. It says nothing about container images or binaries an action downloads at runtime, so assume those are still yours to pin by digest (&lt;code&gt;$REGISTRY/image@sha256:&amp;lt;digest&amp;gt;&lt;/code&gt;). And until the policy is on, any workflow you have not onboarded still floats.&lt;/p&gt;

&lt;h2&gt;
  
  
  How other CI systems handle the same problem
&lt;/h2&gt;

&lt;p&gt;Every CI system has this problem. Here is how pinning looks in three of them.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitLab CI/CD lets you pin &lt;code&gt;include:&lt;/code&gt; references and CI/CD components to a commit SHA. That is pinning per reference: you write each SHA yourself, one line at a time.&lt;/li&gt;
&lt;li&gt;CircleCI orbs are referenced by version. A full semantic version points at a published release, while a major-only reference such as &lt;code&gt;@1&lt;/code&gt; picks up newer releases automatically.&lt;/li&gt;
&lt;li&gt;Jenkins shared libraries load via &lt;code&gt;@Library('name@ref')&lt;/code&gt;, where the ref can be a branch, a tag or a commit. Same model: safe if you choose a commit, floating if you choose a branch.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What GitHub adds is transitive coverage and an admin policy to enforce it, at least on paper. It is still a preview, built with community contributors that GitHub credits by name, including Andrew Nesbitt and William Woodruff, so expect rough edges.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verdict
&lt;/h2&gt;

&lt;p&gt;Adopt it, starting with your most privileged workflows, and wire up the stale check on day one. This is the pinning model Actions should have shipped with: readable references, locked commits, transitive coverage. Just don't confuse a lockfile with a review. It records what you decided to trust. It has no opinion on whether you were right.&lt;/p&gt;

</description>
      <category>githubactions</category>
      <category>supplychain</category>
      <category>lockfiles</category>
      <category>pinning</category>
    </item>
    <item>
      <title>A DevTools pane now draws the ranges behind scroll-triggered animations</title>
      <dc:creator>Leo</dc:creator>
      <pubDate>Fri, 16 Oct 2026 16:07:56 +0000</pubDate>
      <link>https://dev.to/leobaniak/a-devtools-pane-now-draws-the-ranges-behind-scroll-triggered-animations-j5f</link>
      <guid>https://dev.to/leobaniak/a-devtools-pane-now-draws-the-ranges-behind-scroll-triggered-animations-j5f</guid>
      <description>&lt;p&gt;Scroll-triggered animations are hard to debug because the part that matters is invisible. You set a range, you scroll, and the animation either fires or it doesn't. Then you guess which edge you got wrong. Bramus van Damme's new DevTools extension, announced on Bram.us on October 14, puts that part on screen.&lt;/p&gt;

&lt;p&gt;The extension adds a pane called "Scroll-Triggered Animations" to the Elements panel in Chrome DevTools. Select an element and the pane shows an interactive minimap of the scroller, the element and the subject. It handles both timeline types, &lt;code&gt;ScrollTimeline&lt;/code&gt; (&lt;code&gt;scroll()&lt;/code&gt;) and &lt;code&gt;ViewTimeline&lt;/code&gt; (&lt;code&gt;view()&lt;/code&gt;), and the post says it works with &lt;code&gt;position: sticky&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Blue, green and a status light
&lt;/h2&gt;

&lt;p&gt;The ranges are why you'd open it. &lt;code&gt;timeline-trigger-activation-range&lt;/code&gt; is drawn in blue and &lt;code&gt;timeline-trigger-active-range&lt;/code&gt; in green, and you can edit both values live in the pane. Next to them is a status light that goes from red (Inactive) to green (Active) when the trigger activates. That light is what I'd watch first. Say you've scrolled past where the trigger should fire and the light is still red: the problem is your range, and the minimap shows you how far off it is.&lt;/p&gt;

&lt;p&gt;This is also a sequel. Bramus built a Scroll-Driven Animations Debugger extension in 2023, and the new one reuses code from it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rough edges
&lt;/h2&gt;

&lt;p&gt;The post names two limits up front. Ranges written in relative units such as &lt;code&gt;90vh&lt;/code&gt; are shown as resolved pixel values, because that is how the &lt;code&gt;Animation&lt;/code&gt; instance and &lt;code&gt;getComputedStyle()&lt;/code&gt; report them. Elements inside an iframe don't work at all. The pane either shows an error or wrongly reports that there are no animations, and the post traces this to a Chrome DevTools bug, crbug/1480418.&lt;/p&gt;

&lt;p&gt;It is a Chrome DevTools extension distributed through the Chrome Web Store, so it only inspects what Chrome renders. The post doesn't mention tooling for other engines.&lt;/p&gt;

&lt;p&gt;What I'm watching next is that iframe bug, since embedded demos are exactly where it gets in the way. I'm also watching for a similar range view in the devtools of other engines.&lt;/p&gt;

</description>
      <category>scrolltriggeredanimations</category>
      <category>devtools</category>
      <category>cssanimations</category>
      <category>tooling</category>
    </item>
    <item>
      <title>calc-size() and round() stop your background pattern getting chopped at the bottom</title>
      <dc:creator>Leo</dc:creator>
      <pubDate>Thu, 15 Oct 2026 16:08:07 +0000</pubDate>
      <link>https://dev.to/leobaniak/calc-size-and-round-stop-your-background-pattern-getting-chopped-at-the-bottom-92a</link>
      <guid>https://dev.to/leobaniak/calc-size-and-round-stop-your-background-pattern-getting-chopped-at-the-bottom-92a</guid>
      <description>&lt;p&gt;Some ugliness only ever shows up on the last row of a tiled background. The pattern repeats perfectly, the content stops wherever the content stops, and the bottom row of tiles gets sliced in half like a sprite with a broken hitbox. Everyone has shipped it. Most of us shipped it and then just stopped looking at the bottom of that section.&lt;/p&gt;

&lt;p&gt;Ahmad Shadeed ran into exactly this on a section whose pattern repeats on both axes. His write-up on ishadeed.com, published October 15, goes from the quick fix to a better one. The better one combines &lt;code&gt;calc-size()&lt;/code&gt; with &lt;code&gt;round()&lt;/code&gt;, and it's the kind of small, smart CSS move I want to see more of. The big catch goes up front: Shadeed says the solution "works only in Chrome at the time of writing this article."&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;The tile size lives in two custom properties, so everything else can refer to it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.section&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--tile-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;52px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--tile-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;44px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background-image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sx"&gt;url("pattern.png")&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--tile-width&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--tile-height&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The content sets the height, and the tile is 44px tall. Those two numbers almost never line up, so you get the cut-off.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why background-repeat: round loses
&lt;/h2&gt;

&lt;p&gt;The first reflex (Shadeed's too) is &lt;code&gt;background-repeat: round&lt;/code&gt;. It does kill the cut-off. It does that by resizing the tiles until a whole number of them fits the box, so your 52 by 44 pattern stops being 52 by 44. Shadeed's objection is that the &lt;code&gt;round&lt;/code&gt; keyword forces the pattern to shrink or grow, and the "Make it dance" button in his demo makes the wobble impossible to unsee. If the pattern is part of the brand, or lines up with anything else on the page, a tile that changes size with the length of the copy is a bug dressed up as a fix.&lt;/p&gt;

&lt;p&gt;So flip it around. Leave the tile alone and fix the box.&lt;/p&gt;

&lt;h2&gt;
  
  
  Round the box, not the art
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.section&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;calc-size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;up&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--tile-height&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read it from the inside out. &lt;code&gt;calc-size()&lt;/code&gt; takes &lt;code&gt;auto&lt;/code&gt; as its basis and lets you do math on it, with &lt;code&gt;size&lt;/code&gt; standing in for that auto height. Then &lt;code&gt;round(up, size, var(--tile-height))&lt;/code&gt; snaps the result up to the next multiple of the tile height. If the content needs 170px and the tile is 44px, you get 176px. That's four whole rows and no slice.&lt;/p&gt;

&lt;p&gt;It looks like a fixed &lt;code&gt;height&lt;/code&gt;, and it isn't one. Shadeed points out that it behaves like &lt;code&gt;min-height&lt;/code&gt;: the browser works out the auto height first and only then rounds it, so the section still grows with its content. The only input is the tile height, which is why the same line works across different patterns without any retuning.&lt;/p&gt;

&lt;p&gt;Quick detour. Doing math on &lt;code&gt;auto&lt;/code&gt; in plain CSS still gets me. For years the options were measuring in JavaScript or hard-coding a magic number and hoping the copy never changed. OK, back.&lt;/p&gt;

&lt;h2&gt;
  
  
  Up, down, and the padding problem
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;round()&lt;/code&gt; also takes &lt;code&gt;down&lt;/code&gt;. According to Shadeed, rounding up makes the section taller in most cases. Rounding down never adds height you didn't need, and he says he actually prefers &lt;code&gt;down&lt;/code&gt; for the height, while calling the choice "not a big deal."&lt;/p&gt;

&lt;p&gt;Padding is where the choice starts to matter. He rounds the vertical padding to the tile as well:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.section&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;calc-size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;up&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--tile-height&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
  &lt;span class="py"&gt;padding-block&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;up&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--tile-height&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With &lt;code&gt;up&lt;/code&gt;, the &lt;code&gt;1rem&lt;/code&gt; snaps to a full step, so the top and bottom padding each come out equal to the tile height. Round that padding down and the content ends up too close to the edge. Shadeed says that's where it starts to feel odd, so he switches to rounding up. I agree with him: &lt;code&gt;up&lt;/code&gt; is the right default for the padding, and whether the height goes up or down is a matter of taste.&lt;/p&gt;

&lt;h2&gt;
  
  
  The full rule
&lt;/h2&gt;

&lt;p&gt;Here's Shadeed's complete version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.section&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c"&gt;/* This must match the size of the pattern image. */&lt;/span&gt;
  &lt;span class="py"&gt;--tile-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;52px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--tile-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;44px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background-image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sx"&gt;url("pattern.svg")&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--tile-width&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--tile-height&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;padding-block&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;up&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--tile-height&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;calc-size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;up&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--tile-height&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That comment is the only maintenance burden here. Change the artwork and you update two custom properties. Nothing else changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who actually sees it
&lt;/h2&gt;

&lt;p&gt;The support widget in Shadeed's post marks &lt;code&gt;calc-size()&lt;/code&gt; as Limited availability and not Baseline. It's supported in Chrome and Edge, and not in Firefox or Safari. So treat this as progressive enhancement. A browser that can't parse a value drops the whole declaration, which means it ignores that &lt;code&gt;height&lt;/code&gt; line and the section falls back to plain auto height, cut-off and all. Nothing breaks. Those browsers just show what you were shipping yesterday.&lt;/p&gt;

&lt;p&gt;Score: 8 out of 10. It fixes the box instead of bending the artwork, which is almost always the right instinct in CSS. It loses two points for running in only one engine family today.&lt;/p&gt;

</description>
      <category>calcsize</category>
      <category>round</category>
      <category>cssbackgrounds</category>
      <category>csslayout</category>
    </item>
    <item>
      <title>GitLab's Dependency Firewall moves package policy in front of the build</title>
      <dc:creator>Leo</dc:creator>
      <pubDate>Thu, 15 Oct 2026 10:23:37 +0000</pubDate>
      <link>https://dev.to/leobaniak/gitlabs-dependency-firewall-moves-package-policy-in-front-of-the-build-3c8d</link>
      <guid>https://dev.to/leobaniak/gitlabs-dependency-firewall-moves-package-policy-in-front-of-the-build-3c8d</guid>
      <description>&lt;p&gt;GitLab introduced Dependency Firewall in early access at its Transcend event. It is a policy layer that blocks malicious, vulnerable and non-compliant packages before they reach a build. For CI owners, the useful part is where the check runs. Most dependency scanning reports on a package after a job has already installed it. This firewall is meant to stop the package before install, while the job still holds its credentials and nothing hostile has executed.&lt;/p&gt;

&lt;h2&gt;
  
  
  What shipped
&lt;/h2&gt;

&lt;p&gt;Per the GitLab blog, the firewall is available to Premium and Ultimate customers on both GitLab.com and GitLab Self-Managed. It works with GitLab Artifact Central and with external registries from Sonatype Nexus Repository and JFrog Artifactory. Supported package managers at launch are npm, pip, Poetry, Maven, Gradle and Bundler, and GitLab says more are coming.&lt;/p&gt;

&lt;p&gt;Policies can match on four things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;malicious status, drawn from GitLab's malware advisory database&lt;/li&gt;
&lt;li&gt;vulnerability severity, at critical, high, medium or low&lt;/li&gt;
&lt;li&gt;license type, with an allow or deny list&lt;/li&gt;
&lt;li&gt;package age, a minimum age a package must reach before it can enter a build&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each policy runs in one of two modes. Warn mode records what the policy would have caught and lets the build continue. Block mode stops the pipeline on a match and reports the reason. A logged bypass lets a designated user or token proceed anyway, and the bypass is recorded.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the install step is the exposure
&lt;/h2&gt;

&lt;p&gt;GitLab's own research is the case for the feature. In June 2026, its researchers found five malicious PyPI packages, four of them typosquats of Flask, Requests and NumPy. The packages ran at install time and stole CI/CD credentials.&lt;/p&gt;

&lt;p&gt;That shapes the blast radius. A package that executes during &lt;code&gt;pip install&lt;/code&gt; runs inside the job, with whatever tokens, cloud credentials and deploy keys that job can reach. A scanner that flags it in a report afterwards is useful for the incident timeline, but the secrets are already gone. Rotating them is the expensive part of the response, and it usually means paging someone.&lt;/p&gt;

&lt;p&gt;GitLab also notes that AI coding agents now add open source dependencies on their own, often unreviewed. A typosquat that a human reviewer might catch in a diff can reach a merge request that nobody reads line by line.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rolling it out without breaking main
&lt;/h2&gt;

&lt;p&gt;The two modes suggest the order to work in. Start every policy in warn mode, let it run across a normal week of pipelines, and read what it would have blocked. License and severity policies are the likeliest to match dependencies you already ship, and you want to see those matches before they turn into red builds. Move the malicious-status policy to block first. That is the one with the fewest legitimate matches and the most damage if it misses.&lt;/p&gt;

&lt;p&gt;Decide who holds the bypass before you turn on blocking. A bypass that only one person can grant will turn into a 3am bottleneck the first time a hotfix trips the age policy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it will bite
&lt;/h2&gt;

&lt;p&gt;The package-age policy works against urgent patches. When a critical fix lands, the version you need is by definition the newest one, and a minimum-age rule will block it. Plan for that case through the logged bypass, not by loosening the rule under pressure.&lt;/p&gt;

&lt;p&gt;Bypass tokens are credentials. Treat them like any other secret that can push a blocked package into a build. Scope them, rotate them and review the bypass log.&lt;/p&gt;

&lt;p&gt;Coverage is bounded twice. Malicious-status matching is only as current as GitLab's malware advisory database. Ecosystems outside the six listed package managers are not covered yet. During early access, also confirm that every job actually resolves packages through one of the supported registries. A job that installs straight from a public index is outside the gate.&lt;/p&gt;

&lt;h2&gt;
  
  
  How teams gate dependencies elsewhere
&lt;/h2&gt;

&lt;p&gt;Pre-install gating is not new to the package-registry market. Sonatype and JFrog both sell curation products that sit in the repository manager, which is why GitLab's support for Nexus and Artifactory registries matters to teams that already run them.&lt;/p&gt;

&lt;p&gt;Teams without a registry-level gate usually combine smaller controls. Renovate's &lt;code&gt;minimumReleaseAge&lt;/code&gt; setting delays update pull requests until a release has aged, which is the same idea as GitLab's age policy applied at proposal time instead of at install time. Lockfiles with hashes make an install reproducible:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--require-hashes&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
npm ci
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hash pinning stops an artifact from changing under you, but it does nothing about a malicious package that was pinned on purpose. That case still needs a reviewer, or a policy that runs before install.&lt;/p&gt;

</description>
      <category>supplychain</category>
      <category>dependencyfirewall</category>
      <category>gitlab</category>
      <category>packagesecurity</category>
    </item>
  </channel>
</rss>
