<?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: Hojayfa Rahman</title>
    <description>The latest articles on DEV Community by Hojayfa Rahman (@hrmasss).</description>
    <link>https://dev.to/hrmasss</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%2F3961160%2F3523f272-03ac-4b4c-854e-d2728ed091d9.jpeg</url>
      <title>DEV Community: Hojayfa Rahman</title>
      <link>https://dev.to/hrmasss</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hrmasss"/>
    <language>en</language>
    <item>
      <title>aswap: multi-account switching for the Antigravity CLI, with failover when quota runs out</title>
      <dc:creator>Hojayfa Rahman</dc:creator>
      <pubDate>Fri, 02 Oct 2026 06:15:46 +0000</pubDate>
      <link>https://dev.to/hrmasss/aswap-multi-account-switching-for-the-antigravity-cli-with-failover-when-quota-runs-out-1bbj</link>
      <guid>https://dev.to/hrmasss/aswap-multi-account-switching-for-the-antigravity-cli-with-failover-when-quota-runs-out-1bbj</guid>
      <description>&lt;p&gt;I use the Antigravity CLI (&lt;code&gt;agy&lt;/code&gt;) with more than one Google account. Switching meant signing out and back in, and when an account ran out of quota in the middle of a long task, the task just stopped.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/realiti4/claude-swap" rel="noopener noreferrer"&gt;claude-swap&lt;/a&gt; solves exactly this for Claude Code, so I built the same thing for agy: &lt;strong&gt;antigravity-swap&lt;/strong&gt;, command &lt;code&gt;aswap&lt;/code&gt;.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/hrmasss" rel="noopener noreferrer"&gt;
        hrmasss
      &lt;/a&gt; / &lt;a href="https://github.com/hrmasss/antigravity-swap" rel="noopener noreferrer"&gt;
        antigravity-swap
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Multi-account switcher for the Antigravity CLI (agy), with automatic rotation and quota-aware failover
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;antigravity-swap&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;Multi-account switcher for the Antigravity CLI (&lt;code&gt;agy&lt;/code&gt;). Switch between Google accounts without signing out, let &lt;code&gt;aswap&lt;/code&gt; switch for you before an account runs out, see every account's quota in one table, and run long print-mode tasks that carry on to the next account when one hits its limit.&lt;/p&gt;
&lt;p&gt;Inspired by &lt;a href="https://github.com/realiti4/claude-swap" rel="noopener noreferrer"&gt;claude-swap&lt;/a&gt; by Onur Cetinkol, which does the same for Claude Code. The command set, the defaults and the auto-switch design come from it; see &lt;a href="https://github.com/hrmasss/antigravity-swap#credits" rel="noopener noreferrer"&gt;Credits&lt;/a&gt;.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Installation&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Using uv (recommended)&lt;/h3&gt;
&lt;/div&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;uv tool install antigravity-swap&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Using pipx&lt;/h3&gt;

&lt;/div&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;pipx install antigravity-swap&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Standalone binary&lt;/h3&gt;

&lt;/div&gt;
&lt;p&gt;No Python needed. Linux (x86_64, aarch64) and macOS (Apple silicon):&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;curl -fsSL https://raw.githubusercontent.com/hrmasss/antigravity-swap/main/install.sh &lt;span class="pl-k"&gt;|&lt;/span&gt; sh&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Windows (PowerShell):&lt;/p&gt;
&lt;div class="highlight highlight-source-powershell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-smi"&gt;$d&lt;/span&gt; &lt;span class="pl-k"&gt;=&lt;/span&gt; &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;span class="pl-smi"&gt;$&lt;span class="pl-c1"&gt;env:&lt;/span&gt;LOCALAPPDATA&lt;/span&gt;\Programs\aswap&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;; &lt;span class="pl-c1"&gt;New-Item&lt;/span&gt; &lt;span class="pl-k"&gt;-&lt;/span&gt;ItemType Directory &lt;span class="pl-k"&gt;-&lt;/span&gt;Force &lt;span class="pl-smi"&gt;$d&lt;/span&gt; &lt;span class="pl-k"&gt;|&lt;/span&gt; &lt;span class="pl-c1"&gt;Out-Null&lt;/span&gt;
irm https:&lt;span class="pl-k"&gt;//&lt;/span&gt;&lt;span class="pl-c1"&gt;github.com&lt;/span&gt;&lt;span class="pl-k"&gt;/&lt;/span&gt;hrmasss&lt;span class="pl-k"&gt;/&lt;/span&gt;antigravity&lt;span class="pl-k"&gt;-&lt;/span&gt;swap&lt;span class="pl-k"&gt;/&lt;/span&gt;releases&lt;span class="pl-k"&gt;/&lt;/span&gt;latest&lt;span class="pl-k"&gt;/&lt;/span&gt;download&lt;span class="pl-k"&gt;/&lt;/span&gt;&lt;span class="pl-c1"&gt;aswap-windows-x86_64.exe&lt;/span&gt; &lt;span class="pl-k"&gt;-&lt;/span&gt;OutFile &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;…
&lt;/div&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/hrmasss/antigravity-swap" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  Install
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv tool &lt;span class="nb"&gt;install &lt;/span&gt;antigravity-swap
&lt;span class="c"&gt;# or: pipx install antigravity-swap&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are also standalone binaries for Linux, macOS and Windows on the releases page, and a one-line installer for Linux and macOS.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add your accounts
&lt;/h2&gt;

&lt;p&gt;Sign in to agy as usual, then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aswap add
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sign in with the next account (no need to sign out first) and run &lt;code&gt;aswap add&lt;/code&gt; again.&lt;/p&gt;

&lt;h2&gt;
  
  
  See every account at once
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ aswap list
Accounts:
  1: you@example.com [home] (active)
     ├ Gemini 5h:      23%   resets 13:42         in 4h 34m
     ├ Gemini 7d:       5%   resets Oct 8 18:23   in 6d 9h
     ├ Claude/GPT 5h:   0%   full
     └ Claude/GPT 7d:  33%   resets Oct 6 14:06   in 4d 4h
  2: work@example.com [work]
     ├ Gemini 5h:       0%   full
     ├ Gemini 7d:      44%   resets Oct 5 13:02   in 3d 3h
     ├ Claude/GPT 5h:   0%   full
     └ Claude/GPT 7d:   0%   full

Running instances:
  ● TUI    ~/code/app      1: you@example.com [home]   (2 sessions)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;agy meters two pools separately, the Gemini models and the Claude/GPT models, each with a 5-hour and a weekly window. &lt;code&gt;aswap list&lt;/code&gt; shows both, with reset times, and every agy session running on the machine with the account it is on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Switch
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aswap switch            &lt;span class="c"&gt;# next account&lt;/span&gt;
aswap switch work       &lt;span class="c"&gt;# by number, email or alias&lt;/span&gt;
aswap switch &lt;span class="nt"&gt;--strategy&lt;/span&gt; most-quota
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After a switch, aswap runs &lt;code&gt;agy models&lt;/code&gt; to prove the login works. That call lists models and spends no quota.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part I actually wanted: keep a session going
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aswap run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;aswap run&lt;/code&gt; starts agy in your terminal and watches its log. When the account hits its quota, aswap:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;holds that account out until the reset time agy reports,&lt;/li&gt;
&lt;li&gt;picks the next account with the most quota left,&lt;/li&gt;
&lt;li&gt;restarts agy on that account with the same conversation and a short "please continue" prompt.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You see agy restart and pick the task back up. For scripts and unattended runs, &lt;code&gt;aswap exec -- -p "..."&lt;/code&gt; does the same for print mode.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why a restart and not a swap in place?
&lt;/h3&gt;

&lt;p&gt;claude-swap can change the account under a live Claude Code session, because Claude Code re-reads its credentials file. agy keeps its login in memory for the life of the process. I tested it: replacing the login file under a running agy changes nothing. So aswap restarts on the same conversation, which works because agy stores conversations as local files that any account can resume with &lt;code&gt;--conversation &amp;lt;id&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Two details mattered:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;agy retries a quota error a couple of times before giving up. aswap only reacts to the final error that ends the turn, so a retry never kills a healthy session.&lt;/li&gt;
&lt;li&gt;A fresh agy directory opens on the first-run theme picker. aswap carries over the "setup finished" flag, so a restart lands straight in the conversation.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Strategies
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Strategy&lt;/th&gt;
&lt;th&gt;Also called&lt;/th&gt;
&lt;th&gt;Picks&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;best&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;most-quota&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;most quota left (default)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;rotate&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;round-robin&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;next account by number&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;next-available&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;next account that still has quota&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;consume-first&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;soonest-reset&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;the account whose weekly quota resets soonest&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;There is also &lt;code&gt;aswap auto&lt;/code&gt;, which watches the active account and switches before it runs out, with a threshold, hysteresis and a cooldown so two accounts near the limit never ping-pong. That design comes straight from claude-swap.&lt;/p&gt;

&lt;h2&gt;
  
  
  Platforms
&lt;/h2&gt;

&lt;p&gt;On Linux each account gets its own agy directory, so several accounts run side by side. On Windows, agy keeps one login per machine in Credential Manager, so aswap switches that login, and briefly swaps a non-active account in to renew its token so its quota stays visible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Credits
&lt;/h2&gt;

&lt;p&gt;The command set, the defaults and the auto-switch design are borrowed from &lt;a href="https://github.com/realiti4/claude-swap" rel="noopener noreferrer"&gt;claude-swap&lt;/a&gt; by Onur Cetinkol. No code was copied; agy's login and session model needed its own implementation. MIT licensed, issues and PRs welcome.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/hrmasss/antigravity-swap" rel="noopener noreferrer"&gt;https://github.com/hrmasss/antigravity-swap&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;PyPI: &lt;a href="https://pypi.org/project/antigravity-swap/" rel="noopener noreferrer"&gt;https://pypi.org/project/antigravity-swap/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>gemini</category>
      <category>opensource</category>
      <category>python</category>
    </item>
    <item>
      <title>Why your React tournament bracket breaks in Safari (and a 4 KB pure-CSS fix)</title>
      <dc:creator>Hojayfa Rahman</dc:creator>
      <pubDate>Sun, 31 May 2026 12:36:26 +0000</pubDate>
      <link>https://dev.to/hrmasss/why-your-react-tournament-bracket-breaks-in-safari-and-a-4-kb-pure-css-fix-4bj1</link>
      <guid>https://dev.to/hrmasss/why-your-react-tournament-bracket-breaks-in-safari-and-a-4-kb-pure-css-fix-4bj1</guid>
      <description>&lt;p&gt;You build a tournament bracket with a popular React library. In Chrome it's perfect — neat columns, clean connector lines. Then you open it on an iPhone, or in Safari, or inside your Capacitor app… and every match is crammed into the top-left corner, stacked on top of the round headers.&lt;/p&gt;

&lt;p&gt;If you've ever shipped a bracket to iOS, you've probably seen this exact bug. Here's why it happens — and a tiny library that fixes it for good.&lt;/p&gt;

&lt;h2&gt;
  
  
  The symptom
&lt;/h2&gt;

&lt;p&gt;It looks fine everywhere Chromium runs (Chrome, Edge, Android WebView) and completely broken everywhere WebKit runs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Safari (macOS and iOS)&lt;/li&gt;
&lt;li&gt;iOS WKWebView&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Capacitor / Cordova&lt;/strong&gt; apps&lt;/li&gt;
&lt;li&gt;Electron-on-WebKit&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The matches don't just shift a little — they all render at coordinate &lt;code&gt;(0,0)&lt;/code&gt; of the bracket, piling on top of each other and the headers.&lt;/p&gt;

&lt;h2&gt;
  
  
  The cause: SVG &lt;code&gt;&amp;lt;foreignObject&amp;gt;&lt;/code&gt; in WebKit
&lt;/h2&gt;

&lt;p&gt;Most React bracket libraries — &lt;code&gt;@g-loot/react-tournament-brackets&lt;/code&gt;, &lt;code&gt;react-tournament-bracket&lt;/code&gt;, and friends — render the bracket as an &lt;strong&gt;SVG&lt;/strong&gt; and place each match's HTML inside a &lt;code&gt;&amp;lt;foreignObject&amp;gt;&lt;/code&gt; positioned with &lt;code&gt;x&lt;/code&gt;/&lt;code&gt;y&lt;/code&gt; attributes.&lt;/p&gt;

&lt;p&gt;WebKit has a long-standing bug: it &lt;strong&gt;ignores &lt;code&gt;x&lt;/code&gt;, &lt;code&gt;y&lt;/code&gt;, and &lt;code&gt;transform&lt;/code&gt; on &lt;code&gt;&amp;lt;foreignObject&amp;gt;&lt;/code&gt;&lt;/strong&gt; and positions the content relative to the &lt;em&gt;top-level&lt;/em&gt; &lt;code&gt;&amp;lt;svg&amp;gt;&lt;/code&gt; instead of the foreignObject's own coordinates. Every match therefore collapses to the origin.&lt;/p&gt;

&lt;p&gt;And there's no CSS escape hatch — &lt;code&gt;x&lt;/code&gt;, &lt;code&gt;y&lt;/code&gt;, &lt;strong&gt;and&lt;/strong&gt; &lt;code&gt;transform&lt;/code&gt; are all ignored on &lt;code&gt;foreignObject&lt;/code&gt; in Safari, so you can't nudge the content back into place. I even tried patching a library to wrap each match in a &lt;code&gt;&amp;lt;g transform="translate(x,y)"&amp;gt;&lt;/code&gt; instead of a nested &lt;code&gt;&amp;lt;svg x y&amp;gt;&lt;/code&gt;; WebKit ignores ancestor transforms for foreignObject positioning too. The SVG approach is simply a dead end on WebKit.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix: don't use SVG at all
&lt;/h2&gt;

&lt;p&gt;A bracket is really just &lt;strong&gt;columns of cards joined by connector lines&lt;/strong&gt; — and both are expressible in plain CSS.&lt;/p&gt;

&lt;p&gt;Here's the key insight. Put each round in a flex column where every match sits in an equal &lt;code&gt;flex: 1&lt;/code&gt; slot. Because each round has half the matches of the previous one, a match's slot spans exactly two feeder slots — so the two feeders land at &lt;strong&gt;25%&lt;/strong&gt; and &lt;strong&gt;75%&lt;/strong&gt; of that slot, and the match itself at &lt;strong&gt;50%&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Round 1 slot   Round 2 slot
┌──────────┐
│ Match A  │──┐   25%
├──────────┤  ├─►┌──────────┐
│ Match B  │──┘  │ Winner   │   50%
└──────────┘     └──────────┘
┌──────────┐
│ Match C  │──┐   75%
│   ...    │
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Draw the connector elbow with a few absolutely-positioned, bordered &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;s at those same 25 / 50 / 75% offsets, and the tree stays perfectly aligned at any height — with &lt;strong&gt;zero JavaScript measuring&lt;/strong&gt; and &lt;strong&gt;no SVG&lt;/strong&gt;. Because it's all flexbox and borders, it renders identically on Chromium, Firefox, and WebKit.&lt;/p&gt;

&lt;h2&gt;
  
  
  bracketkit
&lt;/h2&gt;

&lt;p&gt;I packaged this up as &lt;strong&gt;&lt;a href="https://github.com/hrmasss/bracketkit" rel="noopener noreferrer"&gt;bracketkit&lt;/a&gt;&lt;/strong&gt; — a headless, pure-CSS tournament bracket for React:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🍏 &lt;strong&gt;Works in Safari / WebKit&lt;/strong&gt; — no SVG, no &lt;code&gt;foreignObject&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;🧩 &lt;strong&gt;Headless&lt;/strong&gt; — &lt;em&gt;you&lt;/em&gt; render the match card; bracketkit owns layout + connectors. No theme objects, no design lock-in.&lt;/li&gt;
&lt;li&gt;🪶 &lt;strong&gt;~4 KB, zero dependencies&lt;/strong&gt; — ESM + CJS + first-class TypeScript types.&lt;/li&gt;
&lt;li&gt;⚡ &lt;strong&gt;SSR-safe&lt;/strong&gt; — no measurement, correct on the first server render.&lt;/li&gt;
&lt;li&gt;🎨 &lt;strong&gt;Style it any way&lt;/strong&gt; — plain CSS, Tailwind, or a drop-in shadcn/ui component.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz1bucmfarvfmhkwxifmu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz1bucmfarvfmhkwxifmu.png" alt=" " width="666" height="592"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Quick start
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i bracketkit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Bracket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;BracketRound&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bracketkit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;home&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;away&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;homeScore&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;awayScore&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rounds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;BracketRound&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Match&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sf&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Semi-finals&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;matches&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="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sf1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;home&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Lions&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;away&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Bears&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;homeScore&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;awayScore&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sf2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;home&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hawks&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;away&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Wolves&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;homeScore&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;awayScore&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;f&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Final&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;f1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;home&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Lions&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;away&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Wolves&lt;/span&gt;&lt;span class="dl"&gt;"&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="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Playoffs&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;overflowX&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;auto&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#64748b&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="cm"&gt;/* connector color */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Bracket&lt;/span&gt;
        &lt;span class="na"&gt;rounds&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;rounds&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;renderRoundHeader&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;round&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;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h3&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;round&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h3&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;renderMatch&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"match-card"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;home&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; — &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;homeScore&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;–&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;away&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; — &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;awayScore&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;–&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;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;h3&gt;
  
  
  Theming
&lt;/h3&gt;

&lt;p&gt;bracketkit ships no visual styling beyond layout. Connectors inherit &lt;code&gt;currentColor&lt;/code&gt; and expose two CSS variables; every part has a &lt;code&gt;data-*&lt;/code&gt; hook:&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="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;data-bracket-root&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--bracket-connector-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#64748b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--bracket-connector-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;data-bracket-match&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c"&gt;/* your card wrapper */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Prefer shadcn/ui?
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx shadcn@latest add https://hrmasss.github.io/bracketkit/r/bracket.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You get a styled, batteries-included &lt;code&gt;&amp;lt;Bracket&amp;gt;&lt;/code&gt; using your shadcn tokens — and you own the code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;🔗 &lt;strong&gt;Live demo &amp;amp; docs:&lt;/strong&gt; &lt;a href="https://hrmasss.github.io/bracketkit/" rel="noopener noreferrer"&gt;https://hrmasss.github.io/bracketkit/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📦 &lt;strong&gt;npm:&lt;/strong&gt; &lt;a href="https://www.npmjs.com/package/bracketkit" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/bracketkit&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;⭐ &lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/hrmasss/bracketkit" rel="noopener noreferrer"&gt;https://github.com/hrmasss/bracketkit&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you've fought the Safari &lt;code&gt;foreignObject&lt;/code&gt; bug, I'd love to know whether this saves you the headache. Issues and PRs welcome.&lt;/p&gt;

</description>
      <category>react</category>
      <category>webdev</category>
      <category>css</category>
      <category>typescript</category>
    </item>
  </channel>
</rss>
