<?xml version="1.0" encoding="UTF-8" ?>
<?xml-stylesheet type="text/xsl" href="/rss.xsl" media="all"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
<channel>
<title>Roastidio.us Tagged with web</title>
<link>https://roastidio.us/tag/2691</link>
<atom:link href="https://roastidio.us/tagged_with/web" rel="self" type="application/rss+xml"></atom:link>
<description>Roastidio.us Tagged with web</description>
<item>
<title>95 reasons for having your own website in 2026 ~ Bell Kiosk</title>
<link>https://bellkiosk.website/blog/reasons-to-website.html</link>
<enclosure type="image/jpeg" length="0" url="https://bellkiosk.website/blog/images/reasons-to-website.png"></enclosure>
<guid isPermaLink="false">sLhOMzMkwZUxDF1c4TJdg0l5bUzWDnPZpjB34w==</guid>
<pubDate>Tue, 21 Jul 2026 07:14:10 +0000</pubDate>
<description>A running list of reasons to have your own website in 2026.</description>
<content:encoded>&lt;ol&gt;
            &lt;li&gt;to write for oneself&lt;/li&gt;
            &lt;li&gt;to post &lt;a href=&quot;https://memory.elliott.computer/posting-quietly/&quot;&gt;quietly&lt;/a&gt;&lt;/li&gt;
            &lt;li&gt;to have a home on the open web&lt;/li&gt;
            &lt;li&gt;to keep a record of the &lt;a href=&quot;https://everythingthathappened.today/&quot;&gt;days&lt;/a&gt;1&lt;/li&gt;
            &lt;li&gt;to learn how the web works&lt;/li&gt;
            &lt;li&gt;to make a &lt;a href=&quot;https://websitesite.xyz&quot;&gt;friend&lt;/a&gt;2 across the world&lt;/li&gt;
            &lt;li&gt;to have a place to keep &lt;a href=&quot;https://memory.elliott.computer/&quot;&gt;memories&lt;/a&gt;3&lt;/li&gt;
            &lt;li&gt;to learn HTML&lt;/li&gt;
            &lt;li&gt;to write lists&lt;/li&gt;
            &lt;li&gt;to keep a daily writing practice&lt;/li&gt;
            &lt;li&gt;to lean into feeling &lt;a href=&quot;https://www.naiveweekly.com/&quot;&gt;naive&lt;/a&gt;4&lt;/li&gt;
            &lt;li&gt;to understand more about your relationship to yourself, to others, or to the world&lt;/li&gt;
            &lt;li&gt;to write poetry&lt;/li&gt;
            &lt;li&gt;to use the web as a medium and material5&lt;/li&gt;
            &lt;li&gt;to ground oneself&lt;/li&gt;
            &lt;li&gt;to remember&lt;/li&gt;
            &lt;li&gt;to make a place for &lt;a href=&quot;https://extrapractice.space/&quot;&gt;conviviality&lt;/a&gt;6&lt;/li&gt;
            &lt;li&gt;to create the community that you didn&amp;#39;t have growing up&lt;/li&gt;
            &lt;li&gt;to make a photo album&lt;/li&gt;
            &lt;li&gt;as a place to rage&lt;/li&gt;
            &lt;li&gt;to recreate your old blogspot or tumblr&lt;/li&gt;
            &lt;li&gt;as a love letter&lt;/li&gt;
            &lt;li&gt;to keep &amp;lt;time&amp;gt;&lt;/li&gt;
            &lt;li&gt;to use &lt;a href=&quot;https://dandelioncoffee.co.uk&quot;&gt;social media&lt;/a&gt;7 a little less&lt;/li&gt;
            &lt;li&gt;to post for oneself&lt;/li&gt;
            &lt;li&gt;to be fragile&lt;/li&gt;
            &lt;li&gt;to &lt;a href=&quot;https://home.elliott.computer/&quot;&gt;ground&lt;/a&gt;8 yourself&lt;/li&gt;
            &lt;li&gt;to collect things and display them on a shelf together&lt;/li&gt;
            &lt;li&gt;to keep things unfinished and open ended&lt;/li&gt;
            &lt;li&gt;to contemplate your own mortality&lt;/li&gt;
            &lt;li&gt;as an activity to do with a friend, &amp;quot;lets make a website together&amp;quot;&lt;/li&gt;
            &lt;li&gt;out of boredom&lt;/li&gt;
            &lt;li&gt;to keep notes&lt;/li&gt;
            &lt;li&gt;as a medium to make a movie&lt;/li&gt;
            &lt;li&gt;an excuse to &lt;a href=&quot;https://oneminutepark.tv/&quot;&gt;go to the park&lt;/a&gt;9&lt;/li&gt;
            &lt;li&gt;because vibe coding only gets you so far&lt;/li&gt;
            &lt;li&gt;for the love of the web&lt;/li&gt;
            &lt;li&gt;to pretend that it&amp;#39;s 2010 again&lt;/li&gt;
            &lt;li&gt;to pretend that it&amp;#39;s 2000 again&lt;/li&gt;
            &lt;li&gt;to become an island&lt;/li&gt;
            &lt;li&gt;to become an archipelago with your friends&lt;/li&gt;
            &lt;li&gt;as a form of prayer&lt;/li&gt;
            &lt;li&gt;to remember that a website is just files &amp;amp; folders&lt;/li&gt;
            &lt;li&gt;as a form of play&lt;/li&gt;
            &lt;li&gt;because you want to publish offline&lt;/li&gt;
            &lt;li&gt;to feel in control&lt;/li&gt;
            &lt;li&gt;to feel out of control&lt;/li&gt;
            &lt;li&gt;to stay unstructured&lt;/li&gt;
            &lt;li&gt;to think through a big life decision&lt;/li&gt;
            &lt;li&gt;to be your own localhost&lt;/li&gt;
            &lt;li&gt;to keep an index of colors&lt;/li&gt;
            &lt;li&gt;to go deep&lt;/li&gt;
            &lt;li&gt;to feel more alive when interacting with a computer&lt;/li&gt;
            &lt;li&gt;to publish without social pressure&lt;/li&gt;
            &lt;li&gt;to keep the like counter at 0&lt;/li&gt;
            &lt;li&gt;to troll yourself&lt;/li&gt;
            &lt;li&gt;to make infinite scrolling optional&lt;/li&gt;
            &lt;li&gt;to bring guest books back&lt;/li&gt;
            &lt;li&gt;to write for yourself&lt;/li&gt;
            &lt;li&gt;to show others what the web could be&lt;/li&gt;
            &lt;li&gt;to world the world&lt;/li&gt;
            &lt;li&gt;as a wish&lt;/li&gt;
            &lt;li&gt;to retain the &lt;a href=&quot;https://en.wikipedia.org/wiki/The_Work_of_Art_in_the_Age_of_Mechanical_Reproduction&quot;&gt;aura&lt;/a&gt;10 of your work&lt;/li&gt;
            &lt;li&gt;to write your own wikipedia entry&lt;/li&gt;
            &lt;li&gt;to remove the &lt;a href=&quot;https://motherfuckingwebsite.com/&quot;&gt;cynicism&lt;/a&gt;11 from the reason&lt;/li&gt;
            &lt;li&gt;to stay grounded in the &lt;a href=&quot;https://pentad.world/&quot;&gt;seasons&lt;/a&gt;12&lt;/li&gt;
            &lt;li&gt;to own what you publish&lt;/li&gt;
            &lt;li&gt;after reading an essay about &lt;a href=&quot;https://thecreativeindependent.com/essays/laurel-schwulst-my-website-is-a-shifting-house-next-to-a-river-of-knowledge-what-could-yours-be/&quot;&gt;what a website could be&lt;/a&gt;13&lt;/li&gt;
            &lt;li&gt;so that the nerds don&amp;#39;t have the only voice on the www&lt;/li&gt;
            &lt;li&gt;as a form of &lt;a href=&quot;https://gijs.garden/&quot;&gt;care&lt;/a&gt;14&lt;/li&gt;
            &lt;li&gt;because it has very little to do with &lt;a href=&quot;https://brutalistwebsites.com/&quot;&gt;brutalism&lt;/a&gt;15&lt;/li&gt;
            &lt;li&gt;because it feels good to be &lt;a href=&quot;https://www.naiveweekly.com/p/alive-internet-theory&quot;&gt;alive&lt;/a&gt;16&lt;/li&gt;
            &lt;li&gt;as a way to find love&lt;/li&gt;
            &lt;li&gt;as a form of protest&lt;/li&gt;
            &lt;li&gt;as a radical statement&lt;/li&gt;
            &lt;li&gt;to have a clean room&lt;/li&gt;
            &lt;li&gt;to be in motion&lt;/li&gt;
            &lt;li&gt;to recharge&lt;/li&gt;
            &lt;li&gt;to draw&lt;/li&gt;
            &lt;li&gt;to try out &lt;a href=&quot;https://indieweb.org/POSSE&quot;&gt;POSSE&lt;/a&gt;17&lt;/li&gt;
            &lt;li&gt;as a way to go &lt;a href=&quot;https://memory.elliott.computer/go-back-to-go-forward/&quot;&gt;forward by looking back&lt;/a&gt;18&lt;/li&gt;
            &lt;li&gt;to bring web rings back&lt;/li&gt;
            &lt;li&gt;to create bridges to &lt;a href=&quot;https://projecting.life/&quot;&gt;other activities&lt;/a&gt;19&lt;/li&gt;
            &lt;li&gt;to feel young and old at the same time&lt;/li&gt;
            &lt;li&gt;to archive &lt;a href=&quot;https://everything.elliott.computer/&quot;&gt;everything&lt;/a&gt;20 you&amp;#39;ve ever done&lt;/li&gt;
            &lt;li&gt;to own your own &lt;a href=&quot;https://camera.elliott.computer/&quot;&gt;instagram grid&lt;/a&gt;21&lt;/li&gt;
            &lt;li&gt;to learn how to &lt;a href=&quot;https://juggling4ever.mmm.page&quot;&gt;juggle&lt;/a&gt;22&lt;/li&gt;
            &lt;li&gt;because no other medium is a container for images, audio, video, text, time, hyperlinks&lt;/li&gt;
            &lt;li&gt;it&amp;#39;s still a largely unexplored artistic medium&lt;/li&gt;
            &lt;li&gt;to make a &lt;a href=&quot;https://fittererr.com/&quot;&gt;soup&lt;/a&gt;23&lt;/li&gt;
            &lt;li&gt;to create a scene report&lt;/li&gt;
            &lt;li&gt;to document a ufo sighting&lt;/li&gt;
            &lt;li&gt;to walk with&lt;/li&gt;
            &lt;li&gt;as a way to make a &lt;a href=&quot;https://sites.elliott.computer/jitterbug.html&quot;&gt;custom home screen&lt;/a&gt; for your phone&lt;/li&gt;
            &lt;li&gt;to publish your music outside the confines of the platforms&lt;/li&gt;
        &lt;/ol&gt;</content:encoded>
</item>
<item>
<title>2026年前期 web-study 参加報告</title>
<link>https://blog.cybozu.io/entry/2026/07/17/133000</link>
<enclosure type="image/jpeg" length="0" url="https://cdn.image.st-hatena.com/image/scale/121400d96a72f84bf49dcc09442f9e5435b7b5be/backend=imagemagick;version=1;width=1300/https%3A%2F%2Fcdn-ak.f.st-hatena.com%2Fimages%2Ffotolife%2Fc%2Fcybozuinsideout%2F20260717%2F20260717133033.png"></enclosure>
<guid isPermaLink="false">WR6qZZpoMYv4MMn-gs3Lrp7ZTogBwLhfhW-sMg==</guid>
<pubDate>Tue, 21 Jul 2026 01:35:32 +0000</pubDate>
<description>この記事は、CYBOZU SUMMER BLOG FES &#39;26の記事です。 こんにちは、フロントエンドエンジニアのmehm8128です。 2026年の前期に、5回にわたりJxckさん主催のweb-studyにサイボウズ社員が参加しました。サイボウズからも何名か登壇しており、5回中2回は会場提供も行ったため、今回は5回のイベントを振り返っていきます。 #tpac_study web-study.connpass.com #tpac_studyでは、2025年11月に神戸で開...</description>
<content:encoded>&lt;p&gt;&lt;span&gt;&lt;img src=&quot;https://cdn-ak.f.st-hatena.com/images/fotolife/c/cybozuinsideout/20260717/20260717133033.png&quot; alt=&quot;2026年前期 web-study 参加報告 mehm8128&quot; title=&quot;&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;この記事は、&lt;a href=&quot;https://summer-blog-fes.cybozu.io/2026/&quot;&gt;CYBOZU SUMMER BLOG FES &amp;#39;26&lt;/a&gt;の記事です。&lt;/p&gt;&lt;p&gt;こんにちは、フロントエンドエンジニアのmehm8128です。&lt;/p&gt;&lt;p&gt;2026年の前期に、5回にわたりJxckさん主催のweb-studyにサイボウズ社員が参加しました。サイボウズからも何名か登壇しており、5回中2回は会場提供も行ったため、今回は5回のイベントを振り返っていきます。&lt;/p&gt;&lt;h2&gt;#tpac_study&lt;/h2&gt;&lt;p&gt;&lt;cite&gt;&lt;a href=&quot;https://web-study.connpass.com/event/378948/&quot;&gt;web-study.connpass.com&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;&lt;p&gt;#tpac_studyでは、2025年11月に神戸で開催されたTPAC2025の参加報告として、現地参加した4名による発表が行われました。
サイボウズのオフィスを提供し、弊社からはsakuさんとSajiさんの2名が登壇しました。&lt;/p&gt;&lt;h3&gt;sakuさん「Drafting the Future of CSS」&lt;/h3&gt;&lt;p&gt;sakuさんからは「標準化」自体や標準化におけるそれぞれのメンバーの役割についての解説や、CSS WGで行われたGrid-Lanes (Masonry) の議論についての発表がありました。&lt;/p&gt;&lt;p&gt;&lt;cite&gt;&lt;a href=&quot;https://sakupi01.github.io/slides/ja/2026_01_20_drafting_the_future_of_css/&quot;&gt;sakupi01.github.io&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;&lt;p&gt;&lt;span&gt;&lt;img src=&quot;https://cdn-ak.f.st-hatena.com/images/fotolife/c/cybozuinsideout/20260717/20260717133004.png&quot; alt=&quot;サイボウズ東京オフィスで発表を行っているsakuさん。「Waterfall Masonryはitem-flow: row？item-flow: column？」というスライド。&quot; title=&quot;&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;h3&gt;Sajiさん「Ambition and Improvement in i18n」&lt;/h3&gt;&lt;p&gt;Sajiさんからはi18n WGで行われたDOM LocalizationやAmount要素についての議論や、それに関連してi18n WGで管理されている&lt;a href=&quot;https://www.w3.org/blog/international/&quot;&gt;Internationalization Activity Blog&lt;/a&gt;というドキュメントについての紹介がありました。&lt;/p&gt;&lt;p&gt;&lt;cite&gt;&lt;a href=&quot;https://speakerdeck.com/sajikix/tpac-study2026&quot;&gt;speakerdeck.com&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;&lt;p&gt;&lt;span&gt;&lt;img src=&quot;https://cdn-ak.f.st-hatena.com/images/fotolife/c/cybozuinsideout/20260717/20260717133009.png&quot; alt=&quot;サイボウズ東京オフィスで発表を行っているSajiさん。「前提となる仕様たち」というスライド。&quot; title=&quot;&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;その他に外部の方2名から、それぞれ縦書きに関する発表「Revisiting Scope Between i18n and CSS」とAIエージェントとPaymentの関係に関する発表「Agents in Payment」が行われました。&lt;/p&gt;&lt;p&gt;TPACでは非常に多くのWGの議論が同時進行で行われていたため、自分の参加したWGとは違うWGに参加した方々の話を詳しく聞くことができたのが良かったです。&lt;/p&gt;&lt;h2&gt;#temporal_study&lt;/h2&gt;&lt;p&gt;&lt;cite&gt;&lt;a href=&quot;https://web-study.connpass.com/event/383606/&quot;&gt;web-study.connpass.com&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;&lt;p&gt;#temporal_studyでは、新しい日時操作APIであるTemporalについて、弊社のSajiさんと、ふぁぼんさんの2名から発表があり、それらに関する質疑応答や議論が行われました。&lt;/p&gt;&lt;h3&gt;Sajiさん「It&amp;#39;s &amp;quot;Time&amp;quot; to use Temporal」&lt;/h3&gt;&lt;p&gt;Sajiさんからは、「そもそも日時にはWallClockTimeとExactTimeの2種類がある」という話や、実際のアプリケーションで使うときにどのようなデータをTemporalのどの型で扱うべきかという点の解説がありました。
また、dayjsやdate-fnsなど既存のライブラリが今後どのようにTemporalをサポートしていくのかという話は、発表後にも少し議論がありました。&lt;/p&gt;&lt;p&gt;&lt;cite&gt;&lt;a href=&quot;https://speakerdeck.com/sajikix/its-time-to-use-temporal&quot;&gt;speakerdeck.com&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;&lt;p&gt;&lt;span&gt;&lt;img src=&quot;https://cdn-ak.f.st-hatena.com/images/fotolife/c/cybozuinsideout/20260717/20260717133014.png&quot; alt=&quot;発表を行っているSajiさん。「よくありそうなデータの変換」というスライド。&quot; title=&quot;&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;h3&gt;ふぁぼんさん「webにとってTemporalとは何か」&lt;/h3&gt;&lt;p&gt;ふぁぼんさんからは、Temporalの基本や仕様に関する詳細な話がありました。
ふぁぼんさん自身が作っているtemporal-polyfill-liteというpolyfillの話や、TemporalがStage 4に到達するまでになぜこんなに時間がかかったのか、仕様の分量やそれを支えるテストケースの話などが解説されました。&lt;/p&gt;&lt;p&gt;&lt;cite&gt;&lt;a href=&quot;https://docswell.com/s/fabon/ZMQ1W9-temporal_study&quot;&gt;docswell.com&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;&lt;p&gt;質疑応答では「Temporalが使えるようになったら、新しくJSを学ぶ人には最初からTemporalを教えるべきなのか？」など、技術だけでなく育成の観点からも今後を見据えた議論が行われました。
また、参加者の中にもブラウザ実装に詳しい有識者の方がいらっしゃり、意見交換が活発に行われました。&lt;/p&gt;&lt;h2&gt;#縦書き_study&lt;/h2&gt;&lt;p&gt;&lt;cite&gt;&lt;a href=&quot;https://web-study.connpass.com/event/387935/&quot;&gt;web-study.connpass.com&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;&lt;p&gt;#縦書き_studyでは会場としてサイボウズのオフィスを提供し、Webにおける縦書きについて外部の方2名による発表が行われました。&lt;/p&gt;&lt;p&gt;itoh_shimonさんからは「&lt;a href=&quot;https://postfixnotation.org/slide/mastodon-vertical-2.pdf&quot;&gt;Mastodonにおける縦書きの諸課題&lt;/a&gt;」及び「&lt;a href=&quot;https://postfixnotation.org/slide/east-asia-vertical-1.pdf&quot;&gt;中国語、韓国語、モンゴル語の縦書き&lt;/a&gt;」の2つの発表がありました。どちらも共通して、右縦書きである中国語・韓国語や左縦書きであるモンゴル語など、外国語に焦点を当てた発表でした。
berlysiaさんからは「&lt;a href=&quot;https://slides.berlysia.net/vertical-writing-study-1/&quot;&gt;縦書きWebの現在地概略&lt;/a&gt;」「&lt;a href=&quot;https://slides.berlysia.net/vertical-writing-study-4/&quot;&gt;縦書きWebを身近なものにするために&lt;/a&gt;」という2つの発表がありました。こちらはどちらかというと日本語のサービスや、「縦書きを使って遊ぶこと」についての発表でした。&lt;/p&gt;&lt;p&gt;発表後は質疑応答や関連したテーマについての雑談などが行われました。&lt;/p&gt;&lt;p&gt;&lt;span&gt;&lt;img src=&quot;https://cdn-ak.f.st-hatena.com/images/fotolife/c/cybozuinsideout/20260717/20260717133019.png&quot; alt=&quot;「今日みなさんにわたす「遊び道具」」というスライド。&quot; title=&quot;&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;h2&gt;#rich_text_editor_study&lt;/h2&gt;&lt;p&gt;&lt;cite&gt;&lt;a href=&quot;https://web-study.connpass.com/event/391357/&quot;&gt;web-study.connpass.com&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;&lt;p&gt;#rich_text_editor_studyでは、4名からリッチテキストエディタに関する発表が行われました。弊社からはコサキンが登壇しました。&lt;/p&gt;&lt;h3&gt;コサキンによる発表&lt;/h3&gt;&lt;p&gt;コサキンからは「RTEライブラリの仕組みとそれを支えるブラウザ機能」というタイトルで、RTEの基本的な仕組みや&lt;code&gt;contenteditable&lt;/code&gt;についての解説がありました。
また、他3名の発表を挟み、「WYSIWYG ルビ入力の概要と Chromium のバグを修正した話」というタイトルで、ルビ入力やそれに関するChromiumのバグ修正に関する発表も行われました。
バグ修正については、&lt;a href=&quot;https://zenn.dev/cybozu_frontend/articles/web-standards-trends-202605#%5Bediting%5D-fix-caret-jumping-to-document-end-on-arrowdown-near-contenteditable%3Dfalse-%3Cruby%3E&quot;&gt;5月版のWeb標準動向&lt;/a&gt;でも紹介しています。&lt;/p&gt;&lt;p&gt;&lt;cite&gt;&lt;a href=&quot;https://speakerdeck.com/karintou8710/rte-raiburarinoshi-zu-mitosorewozhi-eruburauzaji-neng&quot;&gt;speakerdeck.com&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;&lt;p&gt;&lt;cite&gt;&lt;a href=&quot;https://speakerdeck.com/karintou8710/wysiwyg-rubiru-li-nogai-yao-to-chromium-nobaguwoxiu-zheng-sitahua&quot;&gt;speakerdeck.com&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;&lt;p&gt;&lt;span&gt;&lt;img src=&quot;https://cdn-ak.f.st-hatena.com/images/fotolife/c/cybozuinsideout/20260717/20260717133028.png&quot; alt=&quot;発表を行っているコサキン。「なぜDOMを直接扱わないのか？ドキュメントに抽象化する意味は？」というスライド。&quot; title=&quot;&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;他3名からは、それぞれ「フロントエンドだけで戦わない、既存 wiki サービスへの WYSIWYG エディタ導入記」「ページ構造の中で考えるリッチテキスト」「&lt;a href=&quot;https://speakerdeck.com/kirik/tiptapdexiao-zheng-ji-neng-wozuo-tutashi-nikao-etakoto&quot;&gt;Tiptapで校正機能を作った時に考えたこと&lt;/a&gt;」という発表がありました。&lt;/p&gt;&lt;h2&gt;#webfont_study&lt;/h2&gt;&lt;p&gt;&lt;cite&gt;&lt;a href=&quot;https://web-study.connpass.com/event/395240/&quot;&gt;web-study.connpass.com&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;&lt;p&gt;#webfont_studyでは、Webフォントに関する発表が行われました。
新しく仕様が策定されている&lt;a href=&quot;https://www.w3.org/TR/IFT/&quot;&gt;Incremental Font Transfer&lt;/a&gt;というWebフォントの転送方法についての発表や、Noto Sans JPについての話、それらについての質疑応答や議論がありました。
弊社からの登壇や会場提供はありませんでしたが、登壇者からの発表後には意見交換などが行われました。&lt;/p&gt;&lt;p&gt;弊社のプロダクトデザイナーこばりょーによる、参加記事も併せてご覧ください。&lt;/p&gt;&lt;p&gt;&lt;cite&gt;&lt;a href=&quot;https://www.ryota5884.com/posts/20260706_webfont-study%E5%8F%82%E5%8A%A0%E3%83%AD%E3%82%B0&quot;&gt;www.ryota5884.com&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;&lt;h2&gt;まとめ&lt;/h2&gt;&lt;p&gt;個人的に、RTEやWebフォントなどは普段あまり積極的にキャッチアップしない分野なので、話を聞く良い機会になりました。&lt;/p&gt;&lt;p&gt;サイボウズは2025年4月のW3C加入や11月のTPAC参加を機に、Web標準に関するイベントに関わる機会をいただくことが多くなっています。
今後も様々な形で積極的に関わりたいと考えているので、引き続きよろしくお願いします。&lt;/p&gt;&lt;p&gt;また、7/27（月）にも、弊社オフィスでweb-studyが開催される予定なのでこちらも楽しみです。&lt;/p&gt;&lt;p&gt;&lt;cite&gt;&lt;a href=&quot;https://web-study.connpass.com/event/396644/&quot;&gt;web-study.connpass.com&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;</content:encoded>
</item>
<item>
<title>MarkuplintでHTMLの品質を守る</title>
<link>https://blog.cybozu.io/entry/2026/07/09/170000</link>
<enclosure type="image/jpeg" length="0" url="https://cdn.image.st-hatena.com/image/scale/9d4f60dc0f026f5f96ed52cef580a7a64bd0f166/backend=imagemagick;version=1;width=1300/https%3A%2F%2Fcdn-ak.f.st-hatena.com%2Fimages%2Ffotolife%2Fc%2Fcybozuinsideout%2F20260709%2F20260709170010.png"></enclosure>
<guid isPermaLink="false">0Vh_TjaaDR2-Ih9v-sSXaSJGGavkNnaDCiIAoA==</guid>
<pubDate>Tue, 21 Jul 2026 01:35:32 +0000</pubDate>
<description>この記事は、CYBOZU SUMMER BLOG FES &#39;26の記事です。 こんにちは！サイボウズでフロントエンドエンジニアをしている たくぼう です！ はじめに フロントエンドの開発では、JavaScript には ESLint、CSS には Stylelint と、Lint ツールを入れるのが当たり前になっています。 一方で HTML はどうかというと、意外とノーチェックのままになっていないでしょうか。 HTML は多少間違ってい...</description>
<content:encoded>&lt;p&gt;この記事は、&lt;a href=&quot;https://summer-blog-fes.cybozu.io/2026/&quot;&gt;CYBOZU SUMMER BLOG FES &amp;#39;26&lt;/a&gt;の記事です。&lt;/p&gt;&lt;p&gt;こんにちは！サイボウズでフロントエンドエンジニアをしている たくぼう です！&lt;/p&gt;&lt;h2&gt;はじめに&lt;/h2&gt;&lt;p&gt;フロントエンドの開発では、JavaScript には ESLint、CSS には Stylelint と、Lint ツールを入れるのが当たり前になっています。&lt;/p&gt;&lt;p&gt;一方で HTML はどうかというと、意外とノーチェックのままになっていないでしょうか。&lt;/p&gt;&lt;p&gt;HTML は多少間違っていてもブラウザがよしなに表示してくれるので、問題に気づきにくい言語です。&lt;/p&gt;&lt;p&gt;ただ、崩れた入れ子、抜けた &lt;code&gt;alt&lt;/code&gt;、ひも付いていない &lt;code&gt;label&lt;/code&gt; といった小さな乱れは、アクセシビリティや SEO、コードの保守性にじわじわ効いてきます。&lt;/p&gt;&lt;p&gt;この記事では、そんな HTML のための Lint ツールMarkuplintを紹介します。
あわせて記事の後半では、実際にモノレポ構成の kintone へ導入してみた事例も紹介します。&lt;/p&gt;&lt;h2&gt;Markuplintとは&lt;/h2&gt;&lt;p&gt;Markuplint は、その名のとおりマークアップのための Lint ツールです。&lt;/p&gt;&lt;p&gt;&lt;cite&gt;&lt;a href=&quot;https://markuplint.dev/ja/&quot;&gt;markuplint.dev&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;&lt;p&gt;HTML の文法ミスやアクセシビリティ上の問題、非推奨の書き方を静的解析で検出してくれます。&lt;/p&gt;&lt;p&gt;素の HTML だけでなく、JSX（React）や Vue、Svelteといったテンプレートに書かれたマークアップも、後述する仕組みでチェックできます。&lt;/p&gt;&lt;p&gt;もう一つの特徴は、ルールを柔軟に設定できることです。&lt;/p&gt;&lt;p&gt;HTML 仕様への準拠を確認するだけでなく、チームやプロダクト独自のマークアップ規約を Lint として表現する使い方にも向いています。&lt;/p&gt;&lt;h2&gt;何をチェックしてくれるのか&lt;/h2&gt;&lt;p&gt;具体例を挙げてみます。レビューで指摘した覚えのあるものが混じっているのではないでしょうか。&lt;/p&gt;&lt;ul&gt;&lt;li&gt;要素の入れ子の妥当性（&lt;code&gt;span&lt;/code&gt; の中に &lt;code&gt;div&lt;/code&gt; は置けない → &lt;code&gt;permitted-contents&lt;/code&gt;）&lt;/li&gt;&lt;li&gt;必須属性の欠落（&lt;code&gt;img&lt;/code&gt; の &lt;code&gt;alt&lt;/code&gt; → &lt;code&gt;required-attr&lt;/code&gt;）&lt;/li&gt;&lt;li&gt;アクセシビリティまわり（&lt;code&gt;label&lt;/code&gt; との関連付け、&lt;code&gt;table&lt;/code&gt; の &lt;code&gt;caption&lt;/code&gt;、WAI-ARIA の誤用、id の重複）&lt;/li&gt;&lt;li&gt;非推奨の要素・属性&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;たとえば次のように &lt;code&gt;span&lt;/code&gt; の中に &lt;code&gt;div&lt;/code&gt; を書いたとします。&lt;/p&gt;&lt;pre&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;テキスト&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;すると、次のようなエラーが出ます。&lt;/p&gt;&lt;pre&gt;error: このコンテキストでは、要素spanに要素`div`を含めることはできません (permitted-contents)&lt;/pre&gt;&lt;p&gt;エラーメッセージが日本語で出てくれるのも強みの1つです。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;参考&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;https://markuplint.dev/ja/docs/rules&quot;&gt;Rules | Markuplint&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://markuplint.dev/ja/docs/rules/permitted-contents&quot;&gt;permitted-contents | Markuplint&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://markuplint.dev/ja/docs/rules/required-attr&quot;&gt;required-attr | Markuplint&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;ESLintのプラグインとの違い&lt;/h2&gt;&lt;p&gt;&lt;code&gt;required-attr&lt;/code&gt; や &lt;code&gt;wai-aria&lt;/code&gt; のようなチェックは、React であれば &lt;a href=&quot;https://github.com/jsx-eslint/eslint-plugin-jsx-a11y&quot;&gt;eslint-plugin-jsx-a11y&lt;/a&gt; でも一部カバーできます。&lt;/p&gt;&lt;p&gt;「それなら ESLint だけでいいのでは？」と思うかもしれませんが、公式の FAQ でも触れられているとおり、Markuplint ならではの強みがあります。&lt;/p&gt;&lt;ul&gt;&lt;li&gt;要素の親子関係（構造）の適合性チェック（&lt;code&gt;permitted-contents&lt;/code&gt;）ができる&lt;/li&gt;&lt;li&gt;強力なセレクタ機能で、ルールを細かく制御できる&lt;/li&gt;&lt;li&gt;HTML や JSX 以外の構文も幅広くサポートしている&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;一方で&lt;code&gt;onClick&lt;/code&gt; を付けた要素にキーボード操作の考慮を求めるようなイベントハンドラ起点のチェックなど、eslintにしかできないこともあるので併用も可能です。
すでに eslint-plugin-jsx-a11y を入れているプロジェクトでも、置き換えではなく足す形で導入できます。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;参考&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;https://markuplint.dev/ja/docs/guides/faq#htmlhint%E3%82%84eslint-plugin-jsx-a11y%E3%81%A8%E4%BD%95%E3%81%8C%E9%81%95%E3%81%86%E3%81%AE&quot;&gt;HTMLHintやeslint-plugin-jsx-a11yと何が違うの？ | Markuplint&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;導入と設定&lt;/h2&gt;&lt;h3&gt;まずは &lt;code&gt;--init&lt;/code&gt; から&lt;/h3&gt;&lt;p&gt;導入は簡単で、対話形式で設定ファイルを作ってくれる &lt;code&gt;--init&lt;/code&gt; を実行するだけです。&lt;/p&gt;&lt;pre&gt;npx markuplint --init&lt;/pre&gt;&lt;p&gt;使っているテンプレートエンジン（React や Vue など）や、推奨設定を取り込むかどうかを聞かれるので、答えていくと設定ファイル&lt;code&gt;.markuplintrc&lt;/code&gt;が生成されます。&lt;/p&gt;&lt;p&gt;&lt;code&gt;markuplint.config.js&lt;/code&gt; のような形式でも書けます。&lt;/p&gt;&lt;p&gt;設定の土台になるのが、公式の推奨プリセット &lt;code&gt;markuplint:recommended&lt;/code&gt; です。&lt;/p&gt;&lt;p&gt;&lt;code&gt;extends&lt;/code&gt; に指定すると、入れ子の妥当性（&lt;code&gt;permitted-contents&lt;/code&gt;）、必須属性（&lt;code&gt;required-attr&lt;/code&gt;）、WAI-ARIA（&lt;code&gt;wai-aria&lt;/code&gt;）といった主要なルールがまとめて有効になります。&lt;/p&gt;&lt;p&gt;まずはこれを土台にして、あとから必要に応じて足し引きしていくのがおすすめです。React の場合は、後述する React 版のプリセットもあります。&lt;/p&gt;&lt;h3&gt;Parser と Spec の設定&lt;/h3&gt;&lt;p&gt;Markuplint でコンポーネント内のマークアップをチェックするには、&lt;strong&gt;Parser&lt;/strong&gt; と &lt;strong&gt;Spec&lt;/strong&gt; という2つの設定が必要です。&lt;/p&gt;&lt;p&gt;それぞれの役割は次のとおりです。&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;parser&lt;/code&gt;: ファイル拡張子（正規表現）に対して、その構文を解釈する Parser を紐づける&lt;/li&gt;&lt;li&gt;&lt;code&gt;specs&lt;/code&gt;: 同じく拡張子に対して、フレームワーク固有の属性を認識させる&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;この2つを設定に足すだけで、JSX やテンプレートの中のマークアップも正しくチェックできるようになります。&lt;/p&gt;&lt;h3&gt;対応しているフレームワーク&lt;/h3&gt;&lt;p&gt;Parser と Spec は特定のフレームワーク専用ではなく、主要なフレームワーク向けにパッケージが用意されています。&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Vue&lt;/strong&gt;: &lt;code&gt;@markuplint/vue-parser&lt;/code&gt; + &lt;code&gt;@markuplint/vue-spec&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;React&lt;/strong&gt;: &lt;code&gt;@markuplint/jsx-parser&lt;/code&gt; + &lt;code&gt;@markuplint/react-spec&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Svelte&lt;/strong&gt;: &lt;code&gt;@markuplint/svelte-parser&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Astro&lt;/strong&gt;: &lt;code&gt;@markuplint/astro-parser&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Vue・React・Svelte には推奨プリセットも用意されています。&lt;/p&gt;&lt;h3&gt;Reactで使う&lt;/h3&gt;&lt;p&gt;Reactでは、&lt;code&gt;@markuplint/jsx-parser&lt;/code&gt; と &lt;code&gt;@markuplint/react-spec&lt;/code&gt; を導入します。&lt;/p&gt;&lt;pre&gt;npm install -D @markuplint/jsx-parser @markuplint/react-spec&lt;/pre&gt;&lt;p&gt;設定ファイルでは、&lt;code&gt;parser&lt;/code&gt; で &lt;code&gt;.jsx&lt;/code&gt; / &lt;code&gt;.tsx&lt;/code&gt; に JSX Parser を、&lt;code&gt;specs&lt;/code&gt; に React Spec を紐づけます。&lt;/p&gt;&lt;p&gt;React 専用の推奨プリセット &lt;strong&gt;&lt;code&gt;markuplint:recommended-react&lt;/code&gt;&lt;/strong&gt; が用意されているので、&lt;code&gt;extends&lt;/code&gt; にはこれを指定するのが手軽です。&lt;/p&gt;&lt;pre&gt;&lt;span&gt;{&lt;/span&gt;
  &amp;quot;&lt;span&gt;extends&lt;/span&gt;&amp;quot;: &lt;span&gt;[&lt;/span&gt;&amp;quot;&lt;span&gt;markuplint:recommended-react&lt;/span&gt;&amp;quot;&lt;span&gt;]&lt;/span&gt;,
  &amp;quot;&lt;span&gt;parser&lt;/span&gt;&amp;quot;: &lt;span&gt;{&lt;/span&gt;
    &amp;quot;&lt;span&gt;\\.[jt]sx?$&lt;/span&gt;&amp;quot;: &amp;quot;&lt;span&gt;@markuplint/jsx-parser&lt;/span&gt;&amp;quot;
  &lt;span&gt;}&lt;/span&gt;,
  &amp;quot;&lt;span&gt;specs&lt;/span&gt;&amp;quot;: &lt;span&gt;{&lt;/span&gt;
    &amp;quot;&lt;span&gt;\\.[jt]sx?$&lt;/span&gt;&amp;quot;: &amp;quot;&lt;span&gt;@markuplint/react-spec&lt;/span&gt;&amp;quot;
  &lt;span&gt;}&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;React Spec を入れないと、&lt;code&gt;key&lt;/code&gt; のような React 固有の属性が「HTML 仕様に存在しない属性」として誤検知（&lt;code&gt;invalid-attr&lt;/code&gt;）されてしまいます。&lt;/p&gt;&lt;p&gt;Spec を入れることで誤検知が消え、本当に直すべき問題だけが残ります。&lt;/p&gt;&lt;p&gt;なお、先ほどの &lt;code&gt;--init&lt;/code&gt; で&lt;code&gt;React&lt;/code&gt;を選ぶと、このあたりの設定はほぼ自動で書き出されます。&lt;/p&gt;&lt;p&gt;まずは &lt;code&gt;--init&lt;/code&gt; に任せて、生成された設定を見ながら理解していく進め方でも十分です。&lt;/p&gt;&lt;h3&gt;コンポーネントをHTML要素として評価する（pretenders）&lt;/h3&gt;&lt;p&gt;Parser と Spec を入れても、まだチェックしきれないケースがあります。カスタムコンポーネントです。&lt;/p&gt;&lt;p&gt;たとえば &lt;code&gt;li&lt;/code&gt; をコンポーネント化した次のようなコードを考えます。&lt;/p&gt;&lt;pre&gt;&amp;lt;ul&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;ListItem &lt;/span&gt;&lt;span&gt;/&amp;gt;&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;ListItem &lt;/span&gt;&lt;span&gt;/&amp;gt;&lt;/span&gt;
&amp;lt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;ul&amp;gt;&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;Markuplint からは &lt;code&gt;ul&lt;/code&gt; の直下に &lt;code&gt;ListItem&lt;/code&gt; という未知の要素が置かれているように見えるため、本来チェックしたい &lt;code&gt;ul &amp;gt; li&lt;/code&gt; の構造を検証できません。&lt;/p&gt;&lt;p&gt;これを解決するのが &lt;strong&gt;pretenders（プリテンダー機能）&lt;/strong&gt;です。
「このコンポーネントはレンダリングされるとこの要素になる」という対応を設定で教えると、各ルールがコンポーネントをその HTML 要素とみなして評価してくれます。&lt;/p&gt;&lt;pre&gt;&lt;span&gt;{&lt;/span&gt;
  &amp;quot;&lt;span&gt;pretenders&lt;/span&gt;&amp;quot;: &lt;span&gt;[&lt;/span&gt;&lt;span&gt;{&lt;/span&gt; &amp;quot;&lt;span&gt;selector&lt;/span&gt;&amp;quot;: &amp;quot;&lt;span&gt;ListItem&lt;/span&gt;&amp;quot;, &amp;quot;&lt;span&gt;as&lt;/span&gt;&amp;quot;: &amp;quot;&lt;span&gt;li&lt;/span&gt;&amp;quot; &lt;span&gt;}&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;as&lt;/code&gt; はオブジェクト形式でも書けて、レンダリング後の要素が持つ属性（&lt;code&gt;attrs&lt;/code&gt;）や、コンポーネントの属性を引き継ぐか（&lt;code&gt;inheritAttrs&lt;/code&gt;）まで指定できます。&lt;/p&gt;&lt;p&gt;コンポーネントの数だけ設定が増えていくのが難点ですが、まずはエラーが出た箇所から少しずつ足していく運用で十分機能します。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;参考&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;https://markuplint.dev/ja/docs/guides/besides-html&quot;&gt;HTML以外につかう | Markuplint&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://markuplint.dev/ja/docs/configuration/properties&quot;&gt;プロパティを設定する | Markuplint&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;ルールのカスタマイズ&lt;/h2&gt;&lt;p&gt;推奨プリセットのままでも十分使えますが、運用していくとプロジェクトに合わせて調整したくなる場面が出てきます。&lt;/p&gt;&lt;p&gt;いちばん簡単なのは、&lt;code&gt;rules&lt;/code&gt; で個別ルールのオン・オフを切り替えることです。&lt;code&gt;false&lt;/code&gt; で無効、それ以外の値で有効になります。&lt;/p&gt;&lt;pre&gt;&lt;span&gt;{&lt;/span&gt;
  &amp;quot;&lt;span&gt;rules&lt;/span&gt;&amp;quot;: &lt;span&gt;{&lt;/span&gt;
    &amp;quot;&lt;span&gt;required-attr&lt;/span&gt;&amp;quot;: &lt;span&gt;true&lt;/span&gt;,
    &amp;quot;&lt;span&gt;indentation&lt;/span&gt;&amp;quot;: &lt;span&gt;false&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;特定の要素にだけルールを効かせたい場合は、&lt;code&gt;nodeRules&lt;/code&gt;（対象要素そのもの）や &lt;code&gt;childNodeRules&lt;/code&gt;（対象要素の子・子孫）にセレクタで指定します。&lt;/p&gt;&lt;p&gt;たとえば &lt;code&gt;img&lt;/code&gt; に &lt;code&gt;alt&lt;/code&gt; を必須にする場合は次のとおりです。&lt;/p&gt;&lt;pre&gt;&lt;span&gt;{&lt;/span&gt;
  &amp;quot;&lt;span&gt;rules&lt;/span&gt;&amp;quot;: &lt;span&gt;{&lt;/span&gt;
    &amp;quot;&lt;span&gt;required-attr&lt;/span&gt;&amp;quot;: &lt;span&gt;true&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;,
  &amp;quot;&lt;span&gt;nodeRules&lt;/span&gt;&amp;quot;: &lt;span&gt;[&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;
      &amp;quot;&lt;span&gt;selector&lt;/span&gt;&amp;quot;: &amp;quot;&lt;span&gt;img&lt;/span&gt;&amp;quot;,
      &amp;quot;&lt;span&gt;rules&lt;/span&gt;&amp;quot;: &lt;span&gt;{&lt;/span&gt;
        &amp;quot;&lt;span&gt;required-attr&lt;/span&gt;&amp;quot;: &amp;quot;&lt;span&gt;alt&lt;/span&gt;&amp;quot;
      &lt;span&gt;}&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;required-attr&lt;/code&gt; のように要素の種類ごとに設定したいルールは、こうして &lt;code&gt;nodeRules&lt;/code&gt; に書くのが公式でも推奨されている使い方です。&lt;/p&gt;&lt;p&gt;カスタムルールの自作もできますが、組み込みルールのオン・オフと &lt;code&gt;nodeRules&lt;/code&gt; での絞り込みだけでも、かなり実用的な運用ができると思います。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;参考&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;https://markuplint.dev/ja/docs/guides/applying-rules&quot;&gt;ルールを適用する | Markuplint&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;既存プロジェクトへの組み込み&lt;/h2&gt;&lt;p&gt;実際のプロジェクトで動かす方法は大きく二つあります。CI で機械的に回すか、エディタでリアルタイムに指摘してもらうかです。&lt;/p&gt;&lt;p&gt;CI に組み込むなら、&lt;code&gt;package.json&lt;/code&gt; の &lt;code&gt;scripts&lt;/code&gt; にコマンドを足しておくのが素直です。&lt;/p&gt;&lt;pre&gt;&lt;span&gt;{&lt;/span&gt;
  &amp;quot;&lt;span&gt;scripts&lt;/span&gt;&amp;quot;: &lt;span&gt;{&lt;/span&gt;
    &amp;quot;&lt;span&gt;lint:markup&lt;/span&gt;&amp;quot;: &amp;quot;&lt;span&gt;markuplint &lt;/span&gt;&lt;span&gt;\&amp;quot;&lt;/span&gt;&lt;span&gt;./src/**/*.tsx&lt;/span&gt;&lt;span&gt;\&amp;quot;&lt;/span&gt;&amp;quot;
  &lt;span&gt;}&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;これを CI で走らせれば、規約から外れたマークアップがそのままマージされるのを防げます。&lt;/p&gt;&lt;p&gt;あわせて VS Code 拡張（Markuplint）を入れておくと、書いているそばから警告が出るので、CI で弾かれる前に気づけます。&lt;/p&gt;&lt;p&gt;&lt;figure&gt;&lt;span&gt;&lt;img src=&quot;https://cdn-ak.f.st-hatena.com/images/fotolife/c/cybozuinsideout/20260709/20260709170006.png&quot; alt=&quot;&quot; title=&quot;&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;MarkuplintのVScode拡張でrequired-attrのエラーが出ている様子&lt;/figcaption&gt;&lt;/figure&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;参考&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=yusukehirao.vscode-markuplint&quot;&gt;Markuplint - Visual Studio Marketplace&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://github.com/markuplint/markuplint/tree/main/vscode&quot;&gt;VS Code 拡張のソース（markuplint/markuplint リポジトリ vscode/）&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;カスタムルールで独自の規約をチェックする&lt;/h3&gt;&lt;p&gt;Markuplint はカスタムルールも書けます。&lt;/p&gt;&lt;p&gt;たとえば「&lt;code&gt;.js-toggle&lt;/code&gt; クラスを付けた要素には &lt;code&gt;aria-expanded&lt;/code&gt; を必須にする」というチェック。こうしたクラス名と属性のマッピングはチーム固有の知識なので、既存のルールでは表現できません。&lt;/p&gt;&lt;p&gt;カスタムルールは &lt;code&gt;createRule&lt;/code&gt; で定義し、プラグイン（&lt;code&gt;name&lt;/code&gt; + &lt;code&gt;rules&lt;/code&gt;）として包んでエクスポートします。&lt;/p&gt;&lt;pre&gt;&lt;span&gt;// my-plugin.js&lt;/span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt;{&lt;/span&gt; createRule &lt;span&gt;}&lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt;&amp;#39;@markuplint/ml-core&amp;#39;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt;const&lt;/span&gt; requireAriaExpanded &lt;span&gt;=&lt;/span&gt;&lt;span&gt;createRule&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt;verify&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;span&gt;document&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; report &lt;span&gt;})&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt;document&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;walkOn&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;#39;Element&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;el&lt;span&gt;)&lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;
        el&lt;span&gt;.&lt;/span&gt;classList&lt;span&gt;.&lt;/span&gt;&lt;span&gt;contains&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;#39;js-toggle&amp;#39;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;el&lt;span&gt;.&lt;/span&gt;&lt;span&gt;hasAttribute&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;#39;aria-expanded&amp;#39;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;report&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;span&gt;scope&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; el&lt;span&gt;,&lt;/span&gt;&lt;span&gt;message&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;&amp;#39;.js-toggle 要素には aria-expanded が必要です&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;})&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;})&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;})&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt;// プラグイン化: name + rules で包む&lt;/span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt;default&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;&amp;#39;my-rules&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;rules&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;&amp;#39;require-aria-expanded&amp;#39;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; requireAriaExpanded &lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;あとは設定ファイルの &lt;code&gt;plugins&lt;/code&gt; に登録して、&lt;code&gt;プラグイン名/ルール名&lt;/code&gt; で有効化します。&lt;/p&gt;&lt;pre&gt;&lt;span&gt;{&lt;/span&gt;
  &amp;quot;&lt;span&gt;plugins&lt;/span&gt;&amp;quot;: &lt;span&gt;[&lt;/span&gt;&amp;quot;&lt;span&gt;./my-plugin.js&lt;/span&gt;&amp;quot;&lt;span&gt;]&lt;/span&gt;,
  &amp;quot;&lt;span&gt;rules&lt;/span&gt;&amp;quot;: &lt;span&gt;{&lt;/span&gt; &amp;quot;&lt;span&gt;my-rules/require-aria-expanded&lt;/span&gt;&amp;quot;: &lt;span&gt;true&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;これで、チームの規約が組み込みルールと同じようにチェックされます。&lt;/p&gt;&lt;p&gt;規約をコードにしておけば、レビューの目視に頼らず機械的に担保できます。単なる HTML チェッカーを超えて、プロジェクトのマークアップ規約を支える基盤としても使える、というわけです。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;参考&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;https://markuplint.dev/ja/docs/guides/custom-rule&quot;&gt;カスタムルールをつくる | Markuplint&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;kintoneでの活用&lt;/h2&gt;&lt;p&gt;kintone でも、アクセシビリティの確保やコード品質の保証を目的として、Markuplint の導入を試みました。&lt;/p&gt;&lt;p&gt;kintone は複数のパッケージを抱えるモノレポ構成です。&lt;/p&gt;&lt;p&gt;開発フローに乗せるなら、CI で回すだけでなく、VS Code 拡張でエディタ上にリアルタイムに警告が出る状態が理想です。&lt;/p&gt;&lt;h3&gt;モノレポでハマったところ&lt;/h3&gt;&lt;p&gt;リポジトリのルートを VS Code で開くと、拡張が次のような警告を出してうまく動きませんでした。&lt;/p&gt;&lt;pre&gt;Since markuplint could not be found in the node_modules of the workspace,
this use the version (v4.12.0) installed in VS Code Extension.&lt;/pre&gt;&lt;p&gt;kintone のディレクトリ構造は、ざっくり次のようになっています。&lt;/p&gt;&lt;pre&gt;kintone/              # リポジトリのルート
└─ frontend/
   ├─ pudding/        # チームのディレクトリ
   │  ├─ app/         # 領域やパッケージごとのディレクトリ
   │  └─ mobile-app/&lt;/pre&gt;&lt;p&gt;Markuplint が各パッケージの下にインストールされている構成だと、ルートを開いただけでは拡張がそれを見つけられず、プロジェクト固有の設定やプラグインを反映しないままチェックが走ってしまいます。&lt;/p&gt;&lt;p&gt;当面の回避策は、ルートではなく&lt;strong&gt;パッケージのディレクトリを直接開く&lt;/strong&gt;ことです。&lt;/p&gt;&lt;p&gt;ただ、これは v5 系で解消できます。&lt;/p&gt;&lt;p&gt;v5.0.0-alpha.2 で VS Code 拡張に &lt;code&gt;workingDirectories&lt;/code&gt; オプションが追加され、モノレポ内の複数のパッケージをワーキングディレクトリとして指定できるようになりました。これを設定しておけば、ルートを開いたままでも、各パッケージの設定と Markuplint が正しく解決されます。&lt;/p&gt;&lt;p&gt;たとえば &lt;code&gt;app&lt;/code&gt; と &lt;code&gt;mobile-app&lt;/code&gt; を対象にするなら、次のように指定します。&lt;/p&gt;&lt;pre&gt;&lt;span&gt;{&lt;/span&gt;
  &amp;quot;&lt;span&gt;markuplint.workingDirectories&lt;/span&gt;&amp;quot;: &lt;span&gt;[&lt;/span&gt;&amp;quot;&lt;span&gt;app&lt;/span&gt;&amp;quot;, &amp;quot;&lt;span&gt;mobile-app&lt;/span&gt;&amp;quot;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;プレリリースの段階なので今後変わる可能性はありますが、モノレポでエディタ連携を諦めていた場合は、v5 系を試す価値があります。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;参考&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://github.com/markuplint/markuplint/releases/tag/v5.0.0-rc.4&quot;&gt;Release v5.0.0-rc.4 · markuplint/markuplint&lt;/a&gt;&lt;/p&gt;&lt;h2&gt;まとめ&lt;/h2&gt;&lt;p&gt;ESLint や Stylelint と比べると、Markuplint はまだ知名度が高くありません。&lt;/p&gt;&lt;p&gt;カスタムルールまで書けば、チーム固有の規約を組み込みルールと同じ土俵でチェックすることもできます。&lt;/p&gt;&lt;p&gt;HTML のチェックが手つかずになっているプロジェクトには、十分導入する価値があると思います。&lt;/p&gt;&lt;p&gt;ぜひ、まずは推奨プリセットから手軽に始めてみてください。&lt;/p&gt;</content:encoded>
</item>
<item>
<title>Workshop Basel day three</title>
<link>https://daniel.haxx.se/blog/2026/07/16/workshop-basel-day-three/</link>
<guid isPermaLink="false">DCIofVBHXY55m4HBLEAlb9zJLflRFUdNLlfTKQ==</guid>
<pubDate>Mon, 20 Jul 2026 23:10:51 +0000</pubDate>
<description>See also: day one, day two. There is only one thing that is better than two days of HTTP workshop, and that is of course three days of HTTP workshop. The final day of this edition of the series started out with us again shuffling around where we parked ourselves around the big table. Except … Continue reading Workshop Basel day three→</description>
<content:encoded>&lt;p&gt;See also: &lt;a href=&quot;https://daniel.haxx.se/blog/2026/07/14/workshop-basel-day-one/&quot;&gt;day one,&lt;/a&gt;&lt;a href=&quot;https://daniel.haxx.se/blog/2026/07/15/workshop-basel-day-two/&quot;&gt;day two&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;There is only one thing that is better than two days of HTTP workshop, and that is of course &lt;em&gt;three&lt;/em&gt; days of HTTP workshop. The final day of this edition of the series started out with us again shuffling around where we parked ourselves around the big table. Except Mr captain of course who once again got to herd us forward through another day from the same seat.&lt;/p&gt;&lt;h2&gt;&lt;strong&gt;Why MOQ is going to replace HTTP live streaming&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;MOQ (&lt;a href=&quot;https://datatracker.ietf.org/doc/draft-ietf-moq-transport/&quot;&gt;Media over QUIC transport&lt;/a&gt;) is not HTTP, but it uses QUIC so it is at least tangentially interesting and it involves a lot of the same people so this status update still felt welcome and suitable. Compared to existing HTTP based solutions, MOQ is supposed to offer less complexity and lower latency. The moon landing was broadcasted with less latency than current live-streamed TV and maybe MOQ can make us come close to those numbers again. In MOQ clients subscribe to a track that then contains a lot of objects that are delivered. It’s not the request + response approach of HTTP. The fact that this is not HTTP of course brings a lot of questions and well, doubts, and we lingered on various aspects of this topic for quite a while.&lt;/p&gt;&lt;h2&gt;&lt;strong&gt;Reverse HTTP&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;My prize for the best slides of the HTTP workshop 2026 goes to [redacted] for the excellent use of potato images in their presentation.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://datatracker.ietf.org/doc/bofreq-rosomakho-protocol-for-transposed-transactions-over-http-ptth/&quot;&gt;PTTH&lt;/a&gt; is HTTP spelled backwards, commonly pronounced as PoTaToH. A client sets up the connection but the actual HTTP request is sent from the server to the client. One of the intended use cases for this, is to allow an origin server to connect to the CDN proxy and then be able to deliver traffic to the world, rather than to have the CDN connect to the origin the way they usually do. Apparently most CDNs already have custom and proprietary solutions for exactly this kind of feature, so maybe doing it in a standard way instead makes sense?&lt;/p&gt;&lt;h2&gt;&lt;strong&gt;Resumable uploads&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;The &lt;a href=&quot;https://datatracker.ietf.org/doc/draft-ietf-httpbis-resumable-upload/&quot;&gt;draft&lt;/a&gt; explains the new proposed way to continue a previously interrupted upload over HTTP. The upload request gets a Location: header back for the resource being uploaded, and if it gets stopped prematurely, a client can then HEAD that resource, figure out the size and then do a second upload (using the PATCH method) request that tells the server that this transfer should start at offset X.&lt;/p&gt;&lt;p&gt;Exactly how this should be supported in browser’ upload forms seemed a &lt;a href=&quot;https://github.com/whatwg/html/issues/12690&quot;&gt;little bit uncertain&lt;/a&gt;. For my own sake I can see a challenge to implement this nicely for curl in particular when the upload is using formpost upload (curl’s -F flag) which after all still is a very common way to do uploads on the current web. I’ll return to this topic at a later time when I written an implementation to test…&lt;/p&gt;&lt;h2&gt;&lt;strong&gt;io_uring vs. multithreaded server runtimes vs HTTP mismatch&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;io_uring is a Linux asynchronous I/O framework that avoids the overhead of traditional system calls. It uses two shared ring buffers between user space and the kernel, allowing applications to batch I/O operations with zero-copy efficiency.&lt;/p&gt;&lt;p&gt;The feature is disabled by Google in ChromeOS, Android and in production Google servers which certainly holds back some use of it.&lt;/p&gt;&lt;p&gt;io_uring can be helpful to speed up things, but might be complicated to use in existing software architectures and the presentation went into some details on why this is so.&lt;/p&gt;&lt;h2&gt;&lt;strong&gt;Modern UDP I/O for Firefox in Rust&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;A walk-through of some of the recent developments and &lt;a href=&quot;https://mxinden-bot.github.io/slides/02-udp-io/&quot;&gt;improvements in Firefox’s UDP networking stack&lt;/a&gt;. Going from single datagrams to the modern ways to ship large chunks of data offloaded to the kernel to speed things up. Upload throughput in Firefox is up 60-90% over the last 11 releases. Lots of fun graphs and metrics were shown. This work is based on the &lt;a href=&quot;https://crates.io/crates/quinn-udp&quot;&gt;quinn-udp&lt;/a&gt; stack.&lt;/p&gt;&lt;h2&gt;&lt;strong&gt;Rollout of Happy Eyeballs v3 in Firefox&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;&lt;a href=&quot;https://datatracker.ietf.org/doc/draft-ietf-happy-happyeyeballs-v3/&quot;&gt;Happy Eyeballs v3&lt;/a&gt; is coming and &lt;a href=&quot;https://mxinden-bot.github.io/slides/03-hev3-workshop/&quot;&gt;Firefox is implementing it&lt;/a&gt;. It now takes into account many more data sources than before, including alt-svc and HTTPS-RR and races connections against each other to use the one that connects first. There are some recommended timers in the specification and parts of the discussion was around how maybe the timers could instead be tightened a bit, and maybe the delay between the subsequent attempts could then use an exponential backoff instead sticking to a fixed interval?&lt;/p&gt;&lt;p&gt;(I know I’ll discuss some of these details with my curl hacker friends and see what we should adjust… curl already supports most of the Happy Eyeballs v3 specification.)&lt;/p&gt;&lt;h2&gt;&lt;strong&gt;Shorter ones&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;As we approached the end of the day a few shorter topics were ventilated to give us a little more to consider before going home:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Why is there no UTF8 in URIs? “If we would do it again, we would have allowed UTF8 in there” was said by someone who was there in the mid 1990s…&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://www.ietf.org/archive/id/draft-gakiwate-dnsop-optimistic-dns-00.html&quot;&gt;Optimistic DNS&lt;/a&gt; is a draft. Use stale DNS cache data while getting the new. Connection remains alive for 120 seconds while DNS data is often not cached for even 30 seconds. No one in the room seemed to hate it. Let’s do this!&lt;/li&gt;&lt;li&gt;The journey to QUERY. One of the primary authors of the RFC took us through what it took to make it happen. It was sixteen years since the most previous registered HTTP method and maybe this was the last one ever?&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;&lt;strong&gt;The end&lt;/strong&gt; for this time&lt;/h2&gt;&lt;p&gt;With this, the seventh HTTP workshop had ended. Again a very fine event. This time graciously sponsored and arranged by Adobe. Thank you everyone!&lt;/p&gt;&lt;p&gt;The general idea is to continue with these events roughly every second year and I support this. The HTTP workshops are definitely one of my favorite events.&lt;/p&gt;&lt;h2&gt;Credits&lt;/h2&gt;&lt;p&gt;The top image on this post was used in the final presentation and the author told me he is aware of the AI errors in there, “of which there are at least two”.&lt;/p&gt;</content:encoded>
</item>
<item>
<title>Workshop Basel day two</title>
<link>https://daniel.haxx.se/blog/2026/07/15/workshop-basel-day-two/</link>
<guid isPermaLink="false">NZkVmX9oJLR_CJj_PhtQgRknMKB7obbIupvxRQ==</guid>
<pubDate>Mon, 20 Jul 2026 23:10:51 +0000</pubDate>
<description>If you missed it. I already described day one. Caffeinated and ready, we all gathered in the same spacious room as yesterday, but seated in new places as “suggested” by our captain. Some of us even remembered to move over the name tags we wrote yesterday to our new seats. No time was wasted on … Continue reading Workshop Basel day two→</description>
<content:encoded>&lt;p&gt;If you missed it. I already described &lt;a href=&quot;https://daniel.haxx.se/blog/2026/07/14/workshop-basel-day-one/&quot;&gt;day one&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Caffeinated and ready, we all gathered in the same spacious room as yesterday, but seated in new places as “suggested” by our captain. Some of us even remembered to move over the name tags we wrote yesterday to our new seats.&lt;/p&gt;&lt;p&gt;No time was wasted on introductions today. We dove straight in at the deep end.&lt;/p&gt;&lt;h2&gt;&lt;strong&gt;How AI is changing how HTTP is implemented&lt;/strong&gt;.&lt;/h2&gt;&lt;p&gt;Is the future of software that we check-in the AI prompts in the git repository and trust it to generate the correct code? Are specifications the new level o&lt;br/&gt;&lt;br/&gt;f abstraction for source code? These questions triggered long discussions with a huge mix of opinions and experiences getting shared about how AI is used, should be used and could be used now and in the future. &lt;/p&gt;&lt;h2&gt;&lt;strong&gt;Observations and Measurements of HTTP/2 During Large-Scale Web Crawls&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;The Common Crawl spidering upgraded to using HTTP/2 for their scan and as an end result, I believe 61% of the responses used HTTP/2 and the entire round ended a few percent faster than before, which when you traverse a few billion URLs really makes a difference. They apparently use a locally patched version of &lt;a href=&quot;https://nutch.apache.org/&quot;&gt;Apache Nutch&lt;/a&gt; for this.&lt;/p&gt;&lt;h2&gt;&lt;strong&gt;HTTP/1.1 behavior divergence&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;The &lt;a href=&quot;https://www.http-probe.com/entropy&quot;&gt;HTTP probe&lt;/a&gt; project runs a lot of tests on HTTP/1 servers and compares how they behave in a lot of different aspects and then generates these awesome tables. Looks like something for every server implementer team to have a look at and decide what of these red boxes that should rather be converted into green alternatives.&lt;/p&gt;&lt;h2&gt;&lt;strong&gt;Request smuggling test suite &lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;&lt;em&gt;HTTP Zoll&lt;/em&gt; is a newtest suite for intermediaries that tests intermediaries (what we often call proxies) for a large amount of request and response smuggling issues. Some real world problems found were discussed and as this project aims at going Open Source words were expressed on what kind of precautions and checks that maybe should be done first. I hope we get to hear more about this project soon.&lt;/p&gt;&lt;h2&gt;&lt;strong&gt;Server performance &amp;amp; measurement&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;The &lt;a href=&quot;https://www.http-arena.com&quot;&gt;HTTP Arena&lt;/a&gt; is another project that does performance and measurements. They test HTTP server frameworks and present the results in various ways on their site.&lt;/p&gt;&lt;h2&gt;&lt;strong&gt;Increase and evolve HTTP/3 &amp;amp; QUIC&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;In this &lt;a href=&quot;https://mxinden-bot.github.io/slides/04-quic-discussion/&quot;&gt;presentation&lt;/a&gt;, we were presented with different HTTP/3 deployment numbers from different sources and the associated reasoning around why they differ but then more importantly. what can and should be done to increase HTTP/3 usage. &lt;/p&gt;&lt;p&gt;Anti-virus interceptions, enterprise blocks and server-side performance not yet on par with TCP were mentioned as reasons for holding back the numbers.&lt;/p&gt;&lt;p&gt;Reasons for using HTTP/3 include use cases that encourage QUIC adoption: WebTransport, Media over QUIC and MASQUE (HTTP/3 proxies and HTTP/3 proxies over older HTTP proxies). &lt;/p&gt;&lt;p&gt;Using HTTPS-RR for upgrade was &lt;a href=&quot;https://savearoundtrip.com/&quot;&gt;promoted&lt;/a&gt;, as every alt-svc response that is returned with an ALPN using h3 should perhaps also offer h3 over DNS. Why doesn’t your server announce its h3 support over HTTPS-RR?&lt;/p&gt;&lt;p&gt;QUIC v2 is deployed on an amazing 0.003% of all QUIC v1 domains and there was a discussion why this is so and the common sentiment in the room seemed to be that very few saw a reason for deploying v2 and several expressed a concern that doing so might in fact introduce issues. Someone (you can probably guess who) in the room increased that number a lot by quietly mentioning that haxproxy.org certainly supports it.&lt;/p&gt;&lt;h2&gt;&lt;strong&gt;QMUX&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;&lt;a href=&quot;https://www.ietf.org/archive/id/draft-ietf-quic-qmux-02.html&quot;&gt;QUIC multiplexing over bi-directional streams&lt;/a&gt; is a proposal on how to do QUIC-style multiplexing over TLS (or anything else really). It has been adopted by the IETF QUIC working group and there was a somewhat extended discussion about what the HTTPbis group should or should not do with it. The biggest interest might be for data center use, but is that then something IETF should bother about? This is not the first time I blog about this, and even if there did not seem to be a strong demand or need for this, it also did not seem to be completely dead. I bet we will hear more about this later.&lt;/p&gt;&lt;h2&gt;&lt;strong&gt;Multiplexed proxying: challenges in H2 and H3&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;Doing a TLS terminating MITM proxy has its challenges and we were given some insights and experiences on the challenges of doing HTTP/2 and HTTP/3 to the server.&lt;/p&gt;&lt;p&gt;The browsers refuse to do HTTP/3 when they detect custom CA certs installed, which apparently is mostly because of lots of past bad experiences with anti-virus software that in particular seems to break QUIC and for users it is not obvious where the blame should go. This then makes browsers not do HTTP/3 over any MITM proxy.&lt;/p&gt;&lt;p&gt;Some time was spent on how allowing different clients to the proxy uses a shared h2 connection to the target server is complicated and not used, even though in theory it should be possible. An argument was made that it could even lead to worse performance than when using HTTP/1 but I could not quite follow that reasoning. I’m sure I missed some subtle detail in that explanation.&lt;/p&gt;&lt;h2&gt;&lt;strong&gt;Making the Web QUICer with Rapid Start&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;When the afternoon is running late and we have been promised beer and snacks after the final talk, what is better than a hard core technical presentation with lots of graphs and numbers showing how QUIC performance can be improved by tweaking the congestion control algorithm and send more data in the startup phase of a new QUIC connections? This new approach is called &lt;a href=&quot;https://www.ietf.org/archive/id/draft-kazuho-ccwg-rapid-start-02.html&quot;&gt;Rapid Start&lt;/a&gt; and it looks like a promising and yet simple improvement. According to experiments done on real world traffic, the time to last byte was reduced by 14.7% on average. Not bad at all.&lt;/p&gt;&lt;h2&gt;&lt;strong&gt;Drinks and food&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;Our meeting sponsor Adobe graciously sponsored drinks and food so we got to linger around for a few extra hours and talk even more HTTP and networking until the personal firmly insistent they needed us to leave the room and we instead continued solving world problems elsewhere. Topics around the table included the famous HTTP/2 spec coin flip, the QUIC spin bit, the SCONE situation for QUIC, the timeline behind the QUERY method and many more great stories.&lt;/p&gt;&lt;p&gt;Thanks for the beer!&lt;/p&gt;&lt;p&gt;Now we can’t wait for day three.&lt;/p&gt;</content:encoded>
</item>
<item>
<title>Workshop Basel day one | daniel.haxx.se</title>
<link>https://daniel.haxx.se/blog/2026/07/14/workshop-basel-day-one/</link>
<enclosure type="image/jpeg" length="0" url="https://daniel.haxx.se/blog/wp-content/uploads/2026/07/HTTP-ws.jpg"></enclosure>
<guid isPermaLink="false">WkOKBX4P98FdKypIWdatK4T_4BaJOyI2y1XRyw==</guid>
<pubDate>Mon, 20 Jul 2026 23:10:51 +0000</pubDate>
<description>On this hot summer’s day in Basel, Switzerland, the seventh HTTP workshop started. These events tend to work roughly the same way and the people in the room are also to large extent familiar and known since previous editions. Forty people in a meeting room, where we take turns in doing short talks on HTTP … Continue reading Workshop Basel day one →</description>
<content:encoded>&lt;p&gt;On this hot summer’s day in Basel, Switzerland, the seventh HTTP workshop started. These events tend to work roughly the same way and the people in the room are also to large extent familiar and known since previous editions. Forty people in a meeting room, where we take turns in doing short talks on HTTP and networking topics, with the following question and discussion session. The rules for the meetings are explicitly Chatham rules, which means that everything I write about the meeting will be sufficiently fuzzy and without many company or personal names. This is not the kind of meeting that can be easily summed up in a short blog post anyway. You really should be here. &lt;/p&gt;&lt;p&gt;Present in the room were representatives from all the world’s most prominent and used HTTP deployments: clients, browsers, CDNs, proxies and servers. I’m happy to say that there were also several first-timers. We like fresh blood.&lt;/p&gt;&lt;p&gt;(If you think I’m being overly brief or vague about specifics in this post; that is partially on purpose but primarily because I’m a lousy note-taker and mostly write this up after a busy day that also may have involved beer.)&lt;/p&gt;&lt;p&gt;After a round of introductions, we started.&lt;/p&gt;&lt;h2&gt;&lt;strong&gt;Extending REST for State synchronization&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;REST is a set of constraints, and in this presentation it was argued that it can or maybe even should be extended to do more. A number of recent applications like Mastodon/ActivityPub, Bluesky/AT, Matrix, Nostr, IndieWeb, all currently use HTTP to do state synchronization but they all do it differently in their own unique ways. Can REST and maybe HTTP be adjusted to help this for improved interoperability?&lt;/p&gt;&lt;h2&gt;&lt;strong&gt;Last-Modified header use over time&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;Looking at the Common Crawl data and comparing data over time, it was observed that responses use the Last-Modified header field more now than they did in the past, and there were great follow-up speculations on why this is so. Data also shows that a large share of these headers present dates that are almost identical to the time the requests were issued.&lt;/p&gt;&lt;h2&gt;&lt;strong&gt;How is HTTP used in the world?&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;With the &lt;a href=&quot;https://projects.mnot.net/cc-lint/&quot;&gt;cc-lint tool&lt;/a&gt;, data was gathered on how HTTP is actually used today, proving that there is work to be done: deprecated headers are used, some headers are done wrong, and many are overly big. This indicates that there are well used both servers and clients out there that would benefit from cleanup. It probably also shows that doing HTTP correctly and all the correct headers is far from an easy task.&lt;/p&gt;&lt;h2&gt;&lt;strong&gt;AI-bots’ use of HTTP&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;Another presentation showed data, this time from a well-known CDN, on the impact the existing AI scraper bots have on the Internet from their point of view. It showed that roughly half of the requests and half of the bandwidth are spent by scraper bots. A long discussion followed where the numbers were questioned as maybe the numbers look like this because a sufficiently large number of the “bad AI scrapers” appear as regular users to the classifiers. Speculations of different kinds were made. &lt;/p&gt;&lt;h2&gt;&lt;strong&gt;The Apple HTTP stack two years later&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;As a follow-up from a presentation from a previous HTTP workshop we got to learn how the journey on developing their new HTTP stack has progressed and several fun adventures and lessons from that were shared with the audience.&lt;/p&gt;&lt;h2&gt;&lt;strong&gt;Why new HTTP APIs?&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;A look into new &lt;a href=&quot;https://github.com/apple/swift-http-api-proposal&quot;&gt;HTTP API development at Apple&lt;/a&gt;. Some discussions and lessons learned from creating new APIs for both servers and clients.&lt;/p&gt;&lt;h2&gt;&lt;strong&gt;Android Networking&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;We got an excellent walk-through of some details and internals of the Android networking stack. Emphasis was perhaps especially put on ECH and QUIC connection migration, and the final “don’t tell us when your connection closed” led to a long new discussion on how we really should fix the problem: when connection has been left idle for a long time and it is closed by the server, the client (mobile phones) don’t want to be told. This, because getting that RST and more, just wakes up the radio and more on the phone only to tell it to go back to sleep. It was theorized that if we could get rid of this unnecessary battery waste, the accumulated gain across billions of devices would make a serious dent.&lt;/p&gt;&lt;h2&gt;&lt;strong&gt;Day one world problem solving&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;Several additional HTTP related problems were of course also subsequently solved as we then wandered into the city for dinner and maybe a beer. Of course yours truly returned back to his hotel room in good time to be able to write up this blog post.&lt;/p&gt;&lt;p&gt;The best part of these workshops might be the (no pun intended) networking and discussions had completely outside of the agenda.&lt;/p&gt;&lt;p&gt;End of day one. Two more to come,&lt;/p&gt;</content:encoded>
</item>
<item>
<title>I like using scrollbars</title>
<link>https://www.arp242.net/scrollbar.html</link>
<guid isPermaLink="false">UbO85NlR_L_LC7W4X6LPXJsWlCJq2Im-86_PEQ==</guid>
<pubDate>Mon, 20 Jul 2026 11:51:22 +0000</pubDate>
<description>I like to use scrollbars. I move my mouse cursor to the scrollbar handle and move it about – scrolling like this is so much faster than with the wheel if you want to scroll more than a few lines. I didn’t notice how often I did this until sites started breaking this. It seems to be fashionable to put a margin around the entire document, resulting in the scrollbar no longer being aligned to the edge of the screen. This is an absolute bollockache because now you can no longer just move the ...</description>
<content:encoded>&lt;p&gt;I like to use scrollbars. I move my mouse cursor to the scrollbar handle and move it about – scrolling like this is so much faster than with the wheel if you want to scroll more than a few lines.&lt;/p&gt;&lt;p&gt;I didn’t notice how often I did this until sites started breaking this. It seems to be fashionable to put a margin around the entire document, resulting in the scrollbar no longer being aligned to the edge of the screen.&lt;/p&gt;&lt;p&gt;This is an absolute bollockache because now you can no longer just move the mouse to the edge of the screen: you need to place it &lt;em&gt;exactly&lt;/em&gt; on the scrollbar, requiring far greater precision.&lt;/p&gt;&lt;p&gt;You can see an example at &lt;a href=&quot;https://www.arp242.net/scrollmargin.html&quot;&gt;/scrollmargin.html&lt;/a&gt;. You need a device with a mouse cursor to see the problem of course.&lt;/p&gt;&lt;p&gt;Some real examples, all of which started doing it in the last year or so:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;FastMail&lt;/li&gt;&lt;li&gt;Outlook365&lt;/li&gt;&lt;li&gt;Stack Overflow “Beta Redesign”, although that was aborted.&lt;/li&gt;&lt;li&gt;Spotify (&lt;a href=&quot;https://open.spotify.com/artist/0Savi9s4Zz0g4hENYhUP0F&quot;&gt;example&lt;/a&gt;)&lt;/li&gt;&lt;li&gt;GitLab (&lt;a href=&quot;https://gitlab.gnome.org/GNOME/vte/-/issues/253&quot;&gt;example&lt;/a&gt;)&lt;/li&gt;&lt;li&gt;Couchsurfing now &lt;em&gt;completely hides the scrollbar&lt;/em&gt; (&lt;a href=&quot;https://www.couchsurfing.com/c/locations/places&quot;&gt;example&lt;/a&gt;) (aside: I am not saying that Couchsurfing has been infiltrated by AirBnB agents to run this communist woke libtard competition in to the ground, but I am saying that nothing Couchsurfing has done over the last 10 years contradicts that hypothesis.)&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;There is of course also the perennial “inappropriate scrollbar styling” problem, and recently ultra low contrast have also gained popularity; for example:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Substack (&lt;a href=&quot;https://therealitydrift.substack.com/p/reality-drift-in-everyday-life&quot;&gt;example&lt;/a&gt;)&lt;/li&gt;&lt;li&gt;MariaDB documentation (&lt;a href=&quot;https://mariadb.com/docs/server/server-management/variables-and-modes/sql_mode&quot;&gt;example&lt;/a&gt;)&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;I’m not against &lt;em&gt;any&lt;/em&gt; scrollbar styling; in some cases it’s appropriate. Some more playful websites, some very specific and rare webapp use cases, things like that. I’m not a “system behaviour in every single last scenario” purist. The MariaDB reference docs is very much not one of those cases.&lt;/p&gt;&lt;hr/&gt;&lt;p&gt;I like using scrollbars. I can’t be the only person who does. You’re frustrating users for no reason other than following some fad that makes things look ever so marginally nicer. No one pays that much attention to your design and they won’t notice. In ten years time everyone will look back at this in the same way we look back that extremely low contrast text fad from a decade ago: “yeah, that was rather silly”.&lt;/p&gt;</content:encoded>
</item>
<item>
<title>The notification that gave up after a minute | jvalol</title>
<link>https://jva.lol/weblog/the-notification-that-gave-up-after-a-minute/</link>
<enclosure type="image/jpeg" length="0" url="https://jva.lol/og/weblog/the-notification-that-gave-up-after-a-minute/card.png"></enclosure>
<guid isPermaLink="false">q7jIs3j_8uj49svtnBNy3mUPYFs1sP3GEuNlMg==</guid>
<pubDate>Mon, 20 Jul 2026 00:43:15 +0000</pubDate>
<description>Debugging a notification that stopped arriving after a minute: four healthy-looking layers, two quiet failures, one browser timeout.</description>
<content:encoded>&lt;h1&gt;&lt;a href=&quot;https://jva.lol/weblog/the-notification-that-gave-up-after-a-minute/&quot;&gt;The notification that gave up after a minute&lt;/a&gt;&lt;/h1&gt;&lt;div&gt;&lt;a href=&quot;https://jva.lol/categories/projects/&quot;&gt;Projects&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;a href=&quot;https://jva.lol/tags/popsicleboat/&quot;&gt;Popsicleboat&lt;/a&gt;,
&lt;a href=&quot;https://jva.lol/tags/web-push/&quot;&gt;Web-Push&lt;/a&gt;&lt;/div&gt;&lt;p&gt;&lt;time&gt;July 17, 2026&lt;/time&gt;&lt;/p&gt;&lt;p&gt;PopsicleBoat grew browser push notifications recently. The pitch is simple: hear when someone answers you, without handing anyone an email address. Your browser asks permission, a subscription goes to the server, and nothing personal leaves the device. I shipped it, watched the push service accept my first delivery with a tidy &lt;code&gt;201 Created&lt;/code&gt;, and felt good about the whole thing.&lt;/p&gt;&lt;p&gt;Then I replied to one of my own posts from my phone, looked at my laptop, and saw nothing.&lt;/p&gt;&lt;p&gt;No banner. No chime. The reply was on the site, the inbox badge lit up, the notification email arrived — every channel working except the one I’d just built. And here’s what made it a good puzzle: every layer of the push stack, inspected on its own, reported success.&lt;/p&gt;&lt;p&gt;The server sent the notification. The push service accepted it. The subscription was in the database. The service worker was registered. Four green lights, zero banners.&lt;/p&gt;&lt;p&gt;Web push is a relay race with four runners: your server signs a message and hands it to a push service (Google’s, for Chromium browsers), the push service holds it for the browser, the browser wakes a service worker, and the service worker asks the operating system to draw a banner. A dropped baton anywhere shows up the same way — silence at the finish line — and no runner files a report.&lt;/p&gt;&lt;p&gt;The first dropped baton was mine to find in the browser console. &lt;code&gt;Notification.permission&lt;/code&gt; said &lt;code&gt;&amp;quot;default&amp;quot;&lt;/code&gt;. Not &lt;code&gt;&amp;quot;granted&amp;quot;&lt;/code&gt;, not &lt;code&gt;&amp;quot;denied&amp;quot;&lt;/code&gt; — &lt;em&gt;never asked&lt;/em&gt;. Somewhere between enabling notifications and testing them, the site’s permission had ended up back at square one — a site-data sweep, probably — while the subscription it had authorized lived on in my database, perfectly valid, pointing at a browser that would no longer show anything. In that state, a page calling &lt;code&gt;new Notification()&lt;/code&gt; doesn’t error. It does nothing, silently, which is a bold choice for an API whose entire job is being noticed.&lt;/p&gt;&lt;p&gt;One re-grant later (“forever,” this time), banners worked. Victory lasted about an hour, until I noticed pushes still vanished whenever the browser wasn’t running.&lt;/p&gt;&lt;p&gt;That was the second baton, and it was hiding in a default I’d never questioned. Push services will happily hold a message for a browser that’s closed — that’s the whole point of the relay — but only as long as the message’s &lt;em&gt;time-to-live&lt;/em&gt; allows. The library I use sets that TTL to sixty seconds unless told otherwise. Sixty seconds. Close your laptop, get a reply two minutes later, and the push service shrugs and discards it. The &lt;code&gt;201&lt;/code&gt; it returned was entirely honest: message accepted. Nobody promised &lt;em&gt;kept&lt;/em&gt;.&lt;/p&gt;&lt;p&gt;The fix is one option:&lt;/p&gt;&lt;div&gt;&lt;pre&gt;&lt;code class=&quot;language-elixir&quot;&gt;send_notification(payload, message, ttl: 86_400)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;A day. That’s roughly how long “someone answered you” stays worth interrupting someone for; any older and it’s the inbox’s job, which never forgets.&lt;/p&gt;&lt;p&gt;Two lessons sailed home with this one. First: in a layered system where every layer answers “OK,” the bug lives in the gaps between the layers — the permission that reverted between grant and use, the message that expired between accepted and delivered. Debugging meant walking the relay in order and asking each runner not “did you succeed?” but “what did you hand the next runner, and did anyone catch it?”&lt;/p&gt;&lt;p&gt;Second: defaults are decisions someone else made about your product. A conservative browser permission model, a sixty-second TTL — both defensible choices, by people who’d never seen my notification settings page and its promise to &lt;em&gt;hear when someone answers you&lt;/em&gt;. Keeping that promise meant finding every default standing between the reply and the banner, and overruling the ones that disagreed with it.&lt;/p&gt;&lt;p&gt;The banners arrive now. Even the morning after.&lt;/p&gt;&lt;img src=&quot;https://jva.lol/favicon.svg&quot; alt=&quot;&quot; title=&quot;&quot;/&gt;</content:encoded>
</item>
<item>
<title>Guidelines for URL Storage and Comparison</title>
<link>https://textslashplain.com/2026/07/17/guidelines-for-url-storage-and-comparison/</link>
<guid isPermaLink="false">BY-PJ4lzOtWPKtrLOxiYh-ngdn3bUlnZRMsJIQ==</guid>
<pubDate>Sun, 19 Jul 2026 15:51:08 +0000</pubDate>
<description>This document contains some thoughts about the storage and comparison of URLs, common operations crucial to the correct function of security software like Microsoft SmartScreen. Importantly, URLs are also displayed on security surfaces to enable the user to make a decision based on their contents. Eight years ago, I wrote the Guidelines for URL Display.Continue reading &quot;Guidelines for URL Storage and Comparison&quot;</description>
<content:encoded>&lt;p&gt;&lt;em&gt;This document contains some thoughts about the storage and comparison of URLs, common operations crucial to the correct function of security software like &lt;a href=&quot;https://textslashplain.com/2025/04/07/understanding-smartscreen-and-network-protection/&quot;&gt;Microsoft SmartScreen&lt;/a&gt;. &lt;/em&gt;&lt;/p&gt;&lt;p&gt;&lt;em&gt;Importantly, URLs are also displayed on &lt;a href=&quot;https://textslashplain.com/2025/12/03/security-surfaces/&quot;&gt;security surfaces&lt;/a&gt; to enable the user to make a decision based on their contents. Eight years ago, I wrote the &lt;a href=&quot;https://chromium.googlesource.com/chromium/src/+/HEAD/docs/security/url_display_guidelines/url_display_guidelines.md&quot;&gt;Guidelines for URL Display&lt;/a&gt;. &lt;/em&gt;&lt;/p&gt;&lt;h1&gt;Background&lt;/h1&gt;&lt;p&gt;The Web allows linking and retrieval of various resources via an address known as a &lt;strong&gt;URL&lt;/strong&gt;. A URL (Uniform Resource Locator) is an identifier used to locate a resource on the Internet. (&lt;em&gt;Note: “UR&lt;strong&gt;I&lt;/strong&gt;” stands for Uniform Resource Identifier, and in the real world, the two terms are used interchangeably&lt;/em&gt;).&lt;/p&gt;&lt;p&gt;It’s tempting to think of URLs as plain strings because almost all clients (like web browsers) and servers accept URL input as strings. However, despite the existence of various standards for the representation of URLs, there is considerable variation in the handling of URLs that creates inconsistency and variability in the interpretation of URL values. Additional complexity arises because URLs can include seldom-used components that must be understood to properly interpret the URL, and the URL syntax varies between different URL protocol schemes (e.g. HTTPS/HTTP/FTP/mailto/blob/data, etc).&lt;/p&gt;&lt;p&gt;The complexity of URLs is often exploited by attackers, either to socially-engineer users (as in phishing attacks) or to bypass security checks in code.&lt;/p&gt;&lt;h2&gt;URL Encoding as Strings&lt;/h2&gt;&lt;p&gt;In their &lt;strong&gt;canonical form&lt;/strong&gt;, HTTPS/HTTP URLs are meant to contain only a subset of US-ASCII characters, where characters outside of that subset (e.g. emojis; international character sets like Cyrillic, Hiragana, Katakana, Kanji, etc; and ASCII characters like :@/\?# that are &lt;strong&gt;reserved&lt;/strong&gt; for use to delimit URL components) are meant to be &lt;strong&gt;escaped&lt;/strong&gt;. Escaping is a system whereby a character’s UTF-8 octet (byte) representation is serialized to ASCII by preceding each octet’s value with a &lt;strong&gt;%&lt;/strong&gt; character. For example, the URL:&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://webdbg.com/자연생?자연생=ΉЄլլՓ#활의집&quot;&gt;https://webdbg.com/자연생?자연생=ΉЄլլՓ#활의집&lt;/a&gt;&lt;/p&gt;&lt;p&gt;…after escaping, is represented as: &lt;a href=&quot;https://webdbg.com/%EC%9E%90%EC%97%B0%EC%83%9D?%EC%9E%90%EC%97%B0%EC%83%9D=%CE%89%D0%84%D5%AC%D5%AC%D5%93#%ED%99%9C%EC%9D%98%EC%A7%91&quot;&gt;https://webdbg.com/%EC%9E%90%EC%97%B0%EC%83%9D?%EC%9E%90%EC%97%B0%EC%83%9D=%CE%89%D0%84%D5%AC%D5%AC%D5%93#%ED%99%9C%EC%9D%98%EC%A7%91&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Within a browser, you can see both forms:&lt;br/&gt;&lt;br/&gt;&lt;img src=&quot;blob:https://textslashplain.com/b47d13bc-9333-44fe-af19-6cbb7fad2890&quot; alt=&quot;&quot; title=&quot;&quot;/&gt;&lt;br/&gt;&lt;img src=&quot;blob:https://textslashplain.com/45b68cdc-748c-4d25-b9b4-7584b6fab0ca&quot; alt=&quot;&quot; title=&quot;&quot;/&gt;&lt;/p&gt;&lt;p&gt;In this example, the 자 (&lt;a href=&quot;https://www.compart.com/en/unicode/U+C790&quot;&gt;Hangul Syllable Ja&lt;/a&gt;) character has the UTF-8 encoding 0xEC 0x9E 0x90, which is escaped in the URL to the sequence %EC%9E%90, while the Փ (&lt;a href=&quot;https://www.compart.com/en/unicode/U+0553&quot;&gt;Armenian Capital Letter Piwr&lt;/a&gt;) character with the UTF-8 encoding 0xD5 0x93 is escaped to %D5%93.&lt;/p&gt;&lt;p&gt;The hostname of the URL does not use %-escaping, instead relying on a much more complicated escaping mechanism (&lt;a href=&quot;https://en.wikipedia.org/wiki/Punycode&quot;&gt;Punycode&lt;/a&gt;), wherein each DNS label component containing non-ASCII characters is prefixed by xn-- followed by ASCII text that encodes any non-ASCII characters. For instance, a URL containing hostname characters from Thai and Latin character sets:&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://தேடல்.example.baydengrátis.com/자&quot;&gt;https://தேடல்.example.baydengrátis.com/자&lt;/a&gt;&lt;/p&gt;&lt;p&gt;…is encoded as:&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;http://xn--mlcj7bwe6a.example.xn--baydengrtis-r7a.com/%EC%9E%90&quot;&gt;http://xn--mlcj7bwe6a.example.xn--baydengrtis-r7a.com/%EC%9E%90&lt;/a&gt;&lt;/p&gt;&lt;h3&gt;Security Landmine: Inconsistent Client Support&lt;/h3&gt;&lt;p&gt;However, not all clients properly support standards-based encoding behaviors– some clients aim to maintain legacy compatibility with behaviors that existed before the current standards were written.&lt;/p&gt;&lt;p&gt;In particular, Microsoft’s MSHTML (and the WinINET network stack beneath it) used by Internet Explorer, Web Browser Controls (WebOCs) and other common Windows platform features, only implements the standards-based behavior when certain flags are set. Otherwise, MSHTML can put raw UTF-8 octets in the hostname component, and put ANSI codepaged (ACP) octets in the path, query, and fragment components.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;a href=&quot;https://i0.wp.com/textslashplain.com/wp-content/uploads/2026/07/image-1.jpg?ssl=1&quot;&gt;&lt;img src=&quot;https://i0.wp.com/textslashplain.com/wp-content/uploads/2026/07/image-1.jpg?resize=378%2C77&amp;amp;ssl=1&quot; alt=&quot;&quot; title=&quot;&quot;/&gt;&lt;/a&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;The snowman emoji is properly UTF-8 escaped in the path component, and it is thunked down to a question mark ? in the query component (as the target character doesn’t exist in the system codepage). However, if we instead pick &lt;a href=&quot;https://nam06.safelinks.protection.outlook.com/?url=https%3A%2F%2Funicode-table.com%2Fen%2F00CB%2F&amp;amp;data=05%7C01%7CEric.Lawrence%40microsoft.com%7Cac72be8732a54057b99908daa0d8ec51%7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C637999150827935469%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C3000%7C%7C%7C&amp;amp;sdata=FEWxlPs4Cp9xT5%2FnZNGYQEOKRxHtrcv7BIAnXxdb0VQ%3D&amp;amp;reserved=0&quot;&gt;a character&lt;/a&gt; in the system codepage, we see the path component is UTF-8 escaped, but the query string’s raw ACP octet is put out on the wire:&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;a href=&quot;https://i0.wp.com/textslashplain.com/wp-content/uploads/2026/07/image-2.jpg?ssl=1&quot;&gt;&lt;img src=&quot;https://i0.wp.com/textslashplain.com/wp-content/uploads/2026/07/image-2.jpg?resize=403%2C133&amp;amp;ssl=1&quot; alt=&quot;&quot; title=&quot;&quot;/&gt;&lt;/a&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;Furthermore, this path-escaping is sensitive to the checkbox in the Internet Control panel, such that even the path may be sent without escaping if the “Send URL path as UTF-8” checkbox is unticked:&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;a href=&quot;https://i0.wp.com/textslashplain.com/wp-content/uploads/2026/07/image-3.jpg?ssl=1&quot;&gt;&lt;img src=&quot;https://i0.wp.com/textslashplain.com/wp-content/uploads/2026/07/image-3.jpg?resize=344%2C113&amp;amp;ssl=1&quot; alt=&quot;&quot; title=&quot;&quot;/&gt;&lt;/a&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;From &lt;a href=&quot;https://nam06.safelinks.protection.outlook.com/?url=https%3A%2F%2Fstackoverflow.com%2Fa%2F18220123&amp;amp;data=05%7C01%7CEric.Lawrence%40microsoft.com%7Cac72be8732a54057b99908daa0d8ec51%7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C637999150827935469%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C3000%7C%7C%7C&amp;amp;sdata=GccJbvI9VWBXSO%2F3sH%2FWFZhJ1E18PV%2BbgyG75G6loUQ%3D&amp;amp;reserved=0&quot;&gt;https://stackoverflow.com/a/18220123&lt;/a&gt;, variables involved (in the encoding determination) include:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Where the URL was typed (e.g. address bar vs. Start &amp;gt; Run, etc)&lt;/li&gt;&lt;li&gt;What the system’s ANSI codepage is (e.g. what locale the OS uses as default)&lt;/li&gt;&lt;li&gt;The charset of the currently loaded page in the browser&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;WinINET exposes the following option flags to control what octets are “put on the wire”:&lt;/p&gt;&lt;p&gt;&lt;strong&gt;INTERNET_OPTION_CODEPAGE&lt;br/&gt;INTERNET_OPTION_CODEPAGE_PATH&lt;br/&gt;INTERNET_OPTION_CODEPAGE_EXTRA&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;The Default URLMon codepath sets those options inside based on conditional logic, while the EdgeHTML fork of URLMon more simply sets them. Computation of the options is complex: for instance, Edge Legacy checks not only the Zone but also allows a Microsoft-delivered CompatView list to weigh in on the proper encoding.&lt;/p&gt;&lt;p&gt;Beyond the WinINET behavior, it is believed that many other HTTP stacks do not properly handle corner-cases (e.g. being passed URLs that do not follow standards-based escaping rules, contain octet sequences that cannot be validly represented in Unicode, &lt;a href=&quot;https://en.wikipedia.org/wiki/UTF-8#Overlong_encodings&quot;&gt;utilize overlong encodings&lt;/a&gt;, etc.&lt;/p&gt;&lt;h2&gt;URL Components&lt;/h2&gt;&lt;p&gt;A URL is made up of a sequence of &lt;a href=&quot;https://chromium.googlesource.com/chromium/src/+/main/docs/security/url_display_guidelines/url_display_guidelines.md#components-of-urls&quot;&gt;components&lt;/a&gt;. For example, an &lt;strong&gt;absolute&lt;/strong&gt; URL containing all available components for the HTTPS protocol scheme might look like this:&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;a href=&quot;https://i0.wp.com/textslashplain.com/wp-content/uploads/2026/07/image-3.png?ssl=1&quot;&gt;&lt;img src=&quot;https://i0.wp.com/textslashplain.com/wp-content/uploads/2026/07/image-3.png?resize=624%2C134&amp;amp;ssl=1&quot; alt=&quot;&quot; title=&quot;&quot;/&gt;&lt;/a&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;In contrast, a &lt;strong&gt;relative&lt;/strong&gt; URL, as seen within a web page, might be as simple as &lt;code&gt;/file.html&lt;/code&gt;.&lt;/p&gt;&lt;h2&gt;Making URLs Absolute: Combine&lt;/h2&gt;&lt;p&gt;Relative URLs are rarely usable on their own; typically, the first thing that code must do before operating on a relative URL is convert it to an absolute URL by performing a &lt;strong&gt;combine&lt;/strong&gt; operation on the relative URL with the absolute URL of its &lt;strong&gt;context&lt;/strong&gt; (e.g. the web page in which it appears) to generate a new absolute URL. For example, combining &lt;code&gt;https://user:pass@sub.example.com:8080/path?query#fragment&lt;/code&gt; with &lt;code&gt;/file.html&lt;/code&gt; results in an absolute URL of &lt;code&gt;https://user:pass@sub.example.com:8080/file.html&lt;/code&gt;. In this combination operation, the context URL’s path is overwritten, and its query and fragment components are dropped.&lt;/p&gt;&lt;h1&gt;Deep Dive: Components&lt;/h1&gt;&lt;p&gt;Let’s look at each of the URL components and explore how attackers might attempt to confuse code or humans with each component.&lt;/p&gt;&lt;h2&gt;Component: Scheme&lt;/h2&gt;&lt;div&gt;&lt;figure&gt;&lt;a href=&quot;https://i0.wp.com/textslashplain.com/wp-content/uploads/2026/07/image-6.jpg?ssl=1&quot;&gt;&lt;img src=&quot;https://i0.wp.com/textslashplain.com/wp-content/uploads/2026/07/image-6.jpg?resize=112%2C71&amp;amp;ssl=1&quot; alt=&quot;&quot; title=&quot;&quot;/&gt;&lt;/a&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;The scheme component of the URL designates what underlying protocol should be used to retrieve the information, as well as dictating the rules for interpreting the rest of the URL, including whether it uses the standard &lt;strong&gt;hierarchical&lt;/strong&gt; syntax (e.g. HTTP/HTTPS/FTP/FILE) or the &lt;strong&gt;opaque&lt;/strong&gt;&lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc3986.txt&quot;&gt;generic&lt;/a&gt; syntax (e.g. mailto/data/blob).&lt;/p&gt;&lt;p&gt;Clients only support a limited set of URL schemes. Adding new schemes to browsers to &lt;a href=&quot;https://textslashplain.com/2022/01/21/adding-protocol-schemes-to-chromium/&quot;&gt;retrieve resources&lt;/a&gt; or to &lt;a href=&quot;https://textslashplain.com/2019/08/29/web-to-app-communication-app-protocols/&quot;&gt;open external applications&lt;/a&gt; generally requires installing native code; a web-platform mechanism allows &lt;a href=&quot;https://textslashplain.com/2019/11/21/web-to-webapp-communication-custom-scheme-handlers/&quot;&gt;adding schemes from JavaScript&lt;/a&gt;, but when invoked the custom-scheme URL is simply translated into a HTTPS URL for further use.&lt;/p&gt;&lt;h4&gt;Security Considerations: Supported Scope&lt;/h4&gt;&lt;p&gt;Each security-sensitive client must consider how it handles less common URL schemes; many clients will block all URLs except those using popular schemes (HTTP/HTTPS/mailto), but some clients (e.g. browsers) must support invocation of arbitrary URLs.&lt;/p&gt;&lt;p&gt;Because uncommon schemes are a common &lt;a href=&quot;https://textslashplain.com/2020/02/20/bypassing-appprotocol-prompts/&quot;&gt;vector of security compromise&lt;/a&gt;, the decision of how to handle such schemes is an important one. If a security mechanism deems certain schemes out-of-scope, then the user could be exploited by those schemes. For example, mailto: links can be used in phishing:&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;a href=&quot;https://i0.wp.com/textslashplain.com/wp-content/uploads/2026/07/image-7.jpg?ssl=1&quot;&gt;&lt;img src=&quot;https://i0.wp.com/textslashplain.com/wp-content/uploads/2026/07/image-7.jpg?resize=360%2C175&amp;amp;ssl=1&quot; alt=&quot;&quot; title=&quot;&quot;/&gt;&lt;/a&gt;&lt;/figure&gt;&lt;/div&gt;&lt;h4&gt;Security Considerations: Parsing changes by Scheme&lt;/h4&gt;&lt;p&gt;Each feature that attempts to analyze a URL for security purposes must understand the scheme of the URL and the rules by which it is parsed. For example, a mailto URL uses the format &lt;code&gt;mailto:user@host.com?subject=messagecontent&lt;/code&gt; whereby the Internet address appears in the middle of the URL alongside other optional field content.&lt;/p&gt;&lt;h4&gt;Security Considerations: Not All Schemes are Routable&lt;/h4&gt;&lt;p&gt;Some URL schemes do not refer to a server on the internet; the most broadly supported and commonly used of these are the data and blob schemes.&lt;/p&gt;&lt;p&gt;A data: schemed URL &lt;em&gt;contains&lt;/em&gt; the entire resource to which it refers. For example, if a client fetches the URL &lt;code&gt;data:text/html;base64,PGgxPkhlbGxvIHdvcmxkPC9oMT4=&lt;/code&gt;, the result is the string &lt;code&gt;&amp;lt;h1&amp;gt;Hello world&amp;lt;/h1&amp;gt;&lt;/code&gt;, the base-64 decoding of the substring beginning &lt;code&gt;PGg&lt;/code&gt; and ending with&lt;code&gt;4=&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;A blob schemed URL refers to a resource which &lt;em&gt;exists only in the memory&lt;/em&gt; of the JavaScript context that generated the URL via the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL&quot;&gt;createObjectURL()&lt;/a&gt; JavaScript method. JavaScript running inside a web page at &lt;a href=&quot;https://webdbg.com/test/data.htm&quot;&gt;https://webdbg.com/test/data.htm&lt;/a&gt; might generate a blob URL that looks like this:&lt;/p&gt;&lt;p&gt;&lt;code&gt;blob:https://webdbg.com/1c179345-3c59-47bd-908d-2b2b5d198590&lt;/code&gt;&lt;/p&gt;&lt;p&gt;When fetched from JavaScript inside the originating webpage, that blob URL will return an object (anything from an image to a file download to a HTML document). However, attempting to fetch that same blob URL from any other device (or even another browser window on the same device) will not return any content, because the blob scheme is not globally routable.&lt;/p&gt;&lt;h4&gt;Security Considerations: The FILE Scheme is Weird&lt;/h4&gt;&lt;p&gt;The file scheme allows routing a request to a file on either the local file system or the filesystem of a remote server. A URL like &lt;code&gt;file:///C:/test.html&lt;/code&gt; refers to a file on &lt;code&gt;C:&lt;/code&gt; drive of the system where the fetch retrieval occurs, while &lt;code&gt;file://serverhostname/docs/test.html&lt;/code&gt; refers to a file on the share named docs on the server named &lt;code&gt;serverhostname&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;Fortunately, use of the file scheme in modern browsers is &lt;a href=&quot;https://textslashplain.com/2019/10/09/navigating-to-file-urls/&quot;&gt;somewhat restricted&lt;/a&gt; because retrieving file URLs can result in assorted security and privacy badness, including fingerprinting the apps on the user’s computer or leaking the user’s Windows password hash to a remote servers.&lt;/p&gt;&lt;h2&gt;Component: Authority&lt;/h2&gt;&lt;p&gt;The &lt;strong&gt;Authority&lt;/strong&gt; component of the URL consists of three subcomponents: userinfo, the fully-qualified hostname, and the port.&lt;/p&gt;&lt;h3&gt;Component: UserInfo&lt;/h3&gt;&lt;div&gt;&lt;figure&gt;&lt;a href=&quot;https://i0.wp.com/textslashplain.com/wp-content/uploads/2026/07/image-4.jpg?ssl=1&quot;&gt;&lt;img src=&quot;https://i0.wp.com/textslashplain.com/wp-content/uploads/2026/07/image-4.jpg?resize=261%2C81&amp;amp;ssl=1&quot; alt=&quot;&quot; title=&quot;&quot;/&gt;&lt;/a&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;The userinfo subcomponent of a URL specifies a username and password that the client should use when authenticating to a server. This subcomponent is only defined for certain URL schemes (e.g. FTP), while it is officially invalid for others (e.g. HTTP and HTTPS) but nevertheless supported (e.g. Firefox and Chromium allow userinfo for HTTP and HTTPS URLs).&lt;/p&gt;&lt;h4&gt;Security Considerations: UserInfo UI Spoofing&lt;/h4&gt;&lt;p&gt;Way back in Internet Explorer 6, IE started forbidding HTTP/HTTPS URLs containing userinfo because this obscure subcomponent’s primary real-world use was to confuse the user as a part of phishing attacks. Because the UserInfo is typically not present in URLs, a user looking at the URL https://&lt;strong&gt;victim.com&lt;/strong&gt;:80@random.text.&lt;strong&gt;evil.com&lt;/strong&gt;/ will often assume that they are looking at content from victim.com rather than from random.text.evil.com. &lt;/p&gt;&lt;p&gt;This threat vector is not terribly common today: Chromium &lt;a href=&quot;https://chromium.googlesource.com/chromium/src/+/master/docs/security/faq.md#Is-Chrome_s-support-for-userinfo-in-HTTP-URLs-e_g_http_user_password_example_com_considered-a-vulnerability&quot;&gt;hides the UserInfo component&lt;/a&gt; in its address bar, while Firefox explicitly warns the user about this threat:&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;a href=&quot;https://i0.wp.com/textslashplain.com/wp-content/uploads/2026/07/image-5.jpg?ssl=1&quot;&gt;&lt;img src=&quot;https://i0.wp.com/textslashplain.com/wp-content/uploads/2026/07/image-5.jpg?resize=330%2C159&amp;amp;ssl=1&quot; alt=&quot;&quot; title=&quot;&quot;/&gt;&lt;/a&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;I wrote more about the &lt;a href=&quot;https://textslashplain.com/2023/03/22/attack-techniques-spoofing-via-userinfo/&quot;&gt;UserInfo spoofing vector&lt;/a&gt; back in 2023.&lt;/p&gt;&lt;h3&gt;Component: Fully-qualified hostname&lt;/h3&gt;&lt;div&gt;&lt;figure&gt;&lt;a href=&quot;https://i0.wp.com/textslashplain.com/wp-content/uploads/2026/07/image.jpg?ssl=1&quot;&gt;&lt;img src=&quot;https://i0.wp.com/textslashplain.com/wp-content/uploads/2026/07/image.jpg?resize=195%2C146&amp;amp;ssl=1&quot; alt=&quot;&quot; title=&quot;&quot;/&gt;&lt;/a&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;The most security-relevant part of a URL is the fully-qualified hostname of the URL. The hostname is &lt;strong&gt;registered&lt;/strong&gt; by an individual or business (e.g. PayPal, Inc. owns Paypal.com) with the relevant DNS registrar of the top-Level domain (e.g. Verisign controls the .com top-level domain).&lt;/p&gt;&lt;h4&gt;Security Considerations: Transport Security&lt;/h4&gt;&lt;p&gt;If the URL’s scheme is secure (e.g. HTTPS), content delivered from a given hostname is deemed to be under the control of the entity that registered the hostname (modulo compromised infrastructure, etc). However, if the scheme refers to a non-secure protocol like HTTP, and &lt;em&gt;especially&lt;/em&gt; if the protocol traverses an untrusted network, the registrable domain information may not accurately describe the true source of the content because the content may have been modified by a man-in-the-middle on the network. The port number only needs to be specified if it is not the default for the scheme (e.g., 80 for HTTP, 443 for HTTPS).&lt;/p&gt;&lt;h4&gt;Security Considerations: Parsing of IPv6 Literals&lt;/h4&gt;&lt;p&gt;When the URL’s hostname is an IPv6 literal, the address is wrapped in square brackets, e.g. &lt;a href=&quot;http://[::1]/file.html&quot;&gt;&lt;code&gt;http://[::1]:8080/file.html&lt;/code&gt;&lt;/a&gt;  is a reference to a file hosted on port 8080 of the current device’s IPv6 loopback interface. &lt;em&gt;The fact that a colon character can appear&lt;/em&gt; before &lt;em&gt;the colon delimiter representing the start of the Authority’s Port subcomponent can confuse a parser unfamiliar with IPv6 addresses.&lt;/em&gt;&lt;/p&gt;&lt;p&gt;While there’s no standard for including an IPv6 scope id within a URL, &lt;a href=&quot;https://learn.microsoft.com/en-us/windows/win32/wininet/ip-version-6-support&quot;&gt;WinINET allows specification&lt;/a&gt; of the scope by %-encoding the % delimiter character, e.g. &lt;code&gt;https://[::1%253]/&lt;/code&gt;&lt;/p&gt;&lt;h4&gt;Security Considerations: Interpretation of IPv4 Literals non-canonical syntax&lt;/h4&gt;&lt;p&gt;Most technically savvy users are familiar with IPv4 literal addresses in dotted decimal format, like &lt;code&gt;http://127.0.0.2/&lt;/code&gt;.  However, dotted decimal is &lt;a href=&quot;https://www.hacksparrow.com/networking/many-faces-of-ip-address.html&quot;&gt;not the only format&lt;/a&gt;; you can also express the same address by dropping the 0. components, like &lt;code&gt;http://127.2&lt;/code&gt;, with extra 0s like &lt;code&gt;http://127.000000002/&lt;/code&gt;, or in decimal notation &lt;code&gt;http://2130706434/&lt;/code&gt;, octal notation &lt;code&gt;http://0177.0.0.2/&lt;/code&gt;, or hexadecimal notation &lt;code&gt;http://0x7f000002/&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;The various serializations of addresses could be used to evade matching logic. Historically, we’ve also seen some code that assumes that any hostname lacking a dot must not be globally routable and belongs to &lt;a href=&quot;https://learn.microsoft.com/en-us/archive/blogs/ieinternals/the-intranet-zone&quot;&gt;the (more trustworthy) Intranet zone&lt;/a&gt; (leading to an MSRC case for Windows/IE in the early 2000s).&lt;/p&gt;&lt;h4&gt;Security Considerations: IDN and PunyCode&lt;/h4&gt;&lt;p&gt;Support for non-Unicode characters in URLs can lead to spoofing attacks.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://chromium.googlesource.com/chromium/src/+/main/docs/idn.md&quot;&gt;https://chromium.googlesource.com/chromium/src/+/main/docs/idn.md&lt;/a&gt;&lt;/p&gt;&lt;h4&gt;Security Considerations: eTLD+1&lt;/h4&gt;&lt;p&gt;Usually, the security context that the user cares about is the registrable domain of the top-level page’s URL’s origin, even when a given page is made up of components from many different origins. The registrable domain typically consists of a subdomain of an entry on the Public Suffix list. For instance, &lt;code&gt;bbc.co.uk&lt;/code&gt; is a registrable domain under the co.uk public suffix. The fully-qualified hostname consists of a registrable domain, and optionally one or more subdomain labels.&lt;/p&gt;&lt;p&gt;See &lt;a href=&quot;https://publicsuffix.org/&quot;&gt;https://publicsuffix.org/&lt;/a&gt; for more discussion.&lt;/p&gt;&lt;h3&gt;Component: Port&lt;/h3&gt;&lt;div&gt;&lt;figure&gt;&lt;a href=&quot;https://i0.wp.com/textslashplain.com/wp-content/uploads/2026/07/image-5.png?ssl=1&quot;&gt;&lt;img src=&quot;https://i0.wp.com/textslashplain.com/wp-content/uploads/2026/07/image-5.png?resize=64%2C79&amp;amp;ssl=1&quot; alt=&quot;&quot; title=&quot;&quot;/&gt;&lt;/a&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;The port component of the URL indicates which TCP/IP port should be contacted to send the request.&lt;/p&gt;&lt;h4&gt;Security Considerations: Shared Servers and “Well-Known” Ports&lt;/h4&gt;&lt;p&gt;In general, a server operator is deemed to be in control of all ports on the server, although notably some systems (e.g. Unix) allow low-permissioned users to perform TCP/IP listen operations only on certain ports (&amp;gt;1024) while requiring administrative permissions to listen to “low ports” (&amp;lt;1024) which are the default ports used by popular services (HTTP/HTTPS/FTP).&lt;/p&gt;&lt;h4&gt;Security Considerations: Canonicalization Drops Default Port&lt;/h4&gt;&lt;p&gt;When canonicalizing a URL, if the specified target port is the default port for the scheme, it should be removed from the URL entirely. For example,&lt;/p&gt;&lt;figure&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Input URL&lt;/td&gt;&lt;td&gt;Canonical URL&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;a href=&quot;http://example.com/&quot;&gt;http://example.com:80/&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;http://example.com/&quot;&gt;http://example.com/&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;a href=&quot;https://example.com/&quot;&gt;https://example.com:443/&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;https://example.com/&quot;&gt;https://example.com/&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;a href=&quot;ftp://example.com/&quot;&gt;ftp://example.com:21/&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;ftp://example.com/&quot;&gt;ftp://example.com/&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;a href=&quot;https://example.com:4567/&quot;&gt;https://example.com:4567/&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;https://example.com:4567/&quot;&gt;https://example.com:4567/&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;&lt;h2&gt;Meta-Concept: Origin&lt;/h2&gt;&lt;p&gt;The Web Platform security model uses a term called “Origin” which is comprised of the triplet &lt;code&gt;scheme+fullyQualifiedHostname+port&lt;/code&gt;.&lt;/p&gt;&lt;h4&gt;Challenges and Threats&lt;/h4&gt;&lt;p&gt;Malicious websites are motivated to misrepresent their provenance in order to trick visitors into performing an unsafe action (e.g., phishing, malware install) or to otherwise grant unwarranted trust in the information provided by the site (e.g., “fake news”).&lt;/p&gt;&lt;p&gt;Other components of the URL (subdomain, userinfo, path, query, and fragment) are completely under the control of the website and may be crafted in an attempt to spoof the user by misrepresenting the registrable domain.&lt;/p&gt;&lt;h1&gt;URL Comparison&lt;/h1&gt;&lt;p&gt;A critical thing security software needs is a consistent function that turns “&lt;em&gt;a pile of octets that some client is treating as a URL&lt;/em&gt;” into “&lt;em&gt;a string that the security software considers to be the canonical / common form that we will use in all subsequent matching logic, even if that string would not be accepted by a real server.&lt;/em&gt;”&lt;/p&gt;&lt;p&gt;That function will need to handle things like bare-ACP octets appearing anywhere in the string, invalid UTF-16 sequences, raw (non-encoded) UTF-16 codepoints, and anything else we devise. &lt;/p&gt;&lt;h4&gt;Canonicalization and Normalization &lt;/h4&gt;&lt;p&gt;We’ll also need comparison functions that work correctly both with and without the most common forms of canonicalization/normalization performed by servers (e.g. &lt;a href=&quot;https://example.com/thisisafile.htm&quot;&gt;&lt;code&gt;https://example.com/blah/..///thisisafile.htm&lt;/code&gt;&lt;/a&gt; matches &lt;a href=&quot;https://example.com/thisisafile.htm&quot;&gt;&lt;code&gt;https://example.com/thisisafile.htm&lt;/code&gt;&lt;/a&gt;. Similarly, there are many ways to represent equivalent IPv6 literals, and so on.&lt;/p&gt;&lt;h4&gt;Matching and “Rollups”&lt;/h4&gt;&lt;p&gt;Beyond that, the software needs to decide how closely two URLs must match to be considered equivalent.&lt;/p&gt;&lt;p&gt; For example, &lt;code&gt;http://example.com/PaTH&lt;/code&gt; and &lt;code&gt;http://example.com/path&lt;/code&gt; are &lt;em&gt;technically&lt;/em&gt; different URLs, but in actual practice, they will return the same content on from most servers. &lt;/p&gt;&lt;p&gt;While the &lt;code&gt;port&lt;/code&gt; component in the URL is &lt;em&gt;technically &lt;/em&gt;a part of the web origin, in actual practice, it is very uncommon for an arbitrary port to be controlled by a different entity than the default port, and there’s no evidence to suggest that any human being will make a different security decision based on the target port number. As such, security software will often ignore the port when comparing URLs. In Microsoft SmartScreen, for example, a rule set to block &lt;code&gt;https://x.com&lt;/code&gt; (implicitly port &lt;code&gt;443&lt;/code&gt; due to the &lt;code&gt;HTTPS&lt;/code&gt; scheme) will also block requests to &lt;code&gt;http://x.com:12345&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;Any URL matching function needs to decide how closely two URLs must match; in SmartScreen, we call this &lt;strong&gt;roll-up&lt;/strong&gt;, meaning “&lt;em&gt;Will a block for &lt;code&gt;X.com&lt;/code&gt; block &lt;code&gt;X.com/something&lt;/code&gt;? What about &lt;code&gt;sub.x.com/anything&lt;/code&gt;?&lt;/em&gt;” We call these “path rollup” and “domain rollup”, respectively.&lt;/p&gt;&lt;h4&gt;Challenge: Supporting URL “Scrubbing”&lt;/h4&gt;&lt;p&gt;URLs often contain sensitive information, ranging from PII to document titles, to security nonces that are intended to be available only for a single client computer. In an attempt to limit the privacy impact of URL transmission/telemetry, software may attempt to “scrub” the URL, replacing private data with a replacement character (e.g. &lt;code&gt;https://phone.com/425-830-6600/call&lt;/code&gt; is masked as &lt;code&gt;https://phone.com/XXX-XXX-XXXX/call&lt;/code&gt;).&lt;/p&gt;&lt;p&gt;Because scrubbing logic is, at best, based on imprecise heuristics, it is subject to false negatives (sensitive values not masked) and false positives (PII-&lt;em&gt;looking&lt;/em&gt; values that are e.g. actually just meaningless numbers formatted as if they were telephone numbers).&lt;/p&gt;&lt;p&gt;Any attempt at scrubbing creates a mechanism by which an attacker can easily interfere with URL matching logic. The attacker can “cloak” their endpoint such that it responds only with innocuous content when a “PII-looking” value is omitted from the request URL (e.g. by a server-side detonator that is using a scrubbed URL).&lt;/p&gt;&lt;p&gt;Any attempt at using scrubbed values in threat intel feeds creates problems if the components creating and consuming the feed do not recognize how sensitive data is masked within that data, potentially leading to URL matching ambiguities.&lt;/p&gt;&lt;h4&gt;Challenge: Inconsistent Length Limitations&lt;/h4&gt;&lt;p&gt;URLs do not have a consistent length limit. &lt;a href=&quot;https://chromium.googlesource.com/chromium/src/+/main/docs/security/url_display_guidelines/url_display_guidelines.md#URL-Length&quot;&gt;Chromium limits&lt;/a&gt; URLs to 2MB for navigation (and 32kb for display, in some contexts), while various components of Windows use 2083 characters, and some servers and security software have URL length limits that are longer and shorter.&lt;/p&gt;&lt;p&gt;Some security software imposes a lower limit on URL length, returning error pages if the URL length exceeds their limit (e.g. 4096 characters):&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;a href=&quot;https://i0.wp.com/textslashplain.com/wp-content/uploads/2026/07/image-4.png?ssl=1&quot;&gt;&lt;img src=&quot;https://i0.wp.com/textslashplain.com/wp-content/uploads/2026/07/image-4.png?resize=553%2C190&amp;amp;ssl=1&quot; alt=&quot;&quot; title=&quot;&quot;/&gt;&lt;/a&gt;&lt;/figure&gt;&lt;/div&gt;&lt;div&gt;&lt;figure&gt;&lt;a href=&quot;https://i0.wp.com/textslashplain.com/wp-content/uploads/2026/07/image.jpeg?ssl=1&quot;&gt;&lt;img src=&quot;https://i0.wp.com/textslashplain.com/wp-content/uploads/2026/07/image.jpeg?resize=652%2C192&amp;amp;ssl=1&quot; alt=&quot;&quot; title=&quot;&quot;/&gt;&lt;/a&gt;&lt;/figure&gt;&lt;/div&gt;</content:encoded>
</item>
<item>
<title>The fediverse was right · Ata Kuyumcu&#39;s Blog</title>
<link>https://blog.lvmbdv.dev/posts/the-fediverse-was-right/</link>
<enclosure type="image/jpeg" length="0" url="https://blog.lvmbdv.dev/images/federated-social-media.jpg"></enclosure>
<guid isPermaLink="false">y0zy6KUQbYy-SjF89heomb_XOQBpmm9HuyjJiA==</guid>
<pubDate>Sun, 19 Jul 2026 14:16:11 +0000</pubDate>
<description>Federated services are the only escape from corporate platforms and lonely self-hosting. ActivityPub works. Git forges are next. AI just made protocols cheap enough to iterate on.</description>
<content:encoded>&lt;p&gt;I use &lt;a href=&quot;https://joinmastodon.org/&quot;&gt;Mastodon&lt;/a&gt; on and off but I would not trade it
for any alternative atm. Not because it’s better than Twitter was, the
discoverability is worse, the reply threading is too complex at times, and it
does have sync issues across instances. What it does have is something no
corporate platform will ever offer: you can leave and take your social graph
with you. Your followers, your follows, your block lists. Pack them up, point
your domain at a different server, done.&lt;/p&gt;&lt;p&gt;That’s not a selling-point, it’s the absence of a lock-in mechanism. And it only
works because &lt;a href=&quot;https://activitypub.rocks/&quot;&gt;ActivityPub&lt;/a&gt; is a real protocol that
people actually implement.&lt;/p&gt;&lt;p&gt;We’re about to see a lot more of this.&lt;/p&gt;&lt;h2&gt;Protocols that ship&lt;/h2&gt;&lt;p&gt;ActivityPub shipped. That’s the part people gloss over. The fediverse has
millions of users across thousands of instances. It’s not a whitepaper. It’s not
a “we’re exploring decentralized architectures” blog post from a company that’ll
pivot to AI next quarter. People use it every day, and they use it to talk to
people on completely different servers running completely different software.
Mastodon talking to &lt;a href=&quot;https://pleroma.social/&quot;&gt;Pleroma&lt;/a&gt; talking to
&lt;a href=&quot;https://pixelfed.org/&quot;&gt;Pixelfed&lt;/a&gt; talking to
&lt;a href=&quot;https://joinpeertube.org/&quot;&gt;PeerTube&lt;/a&gt;. Same protocol, different UIs, different
moderation policies, different vibes.&lt;/p&gt;&lt;p&gt;The protocol layer is the part that matters. Implementations come and go.
Mastodon might fade. Something better will replace it. But ActivityPub will
still be there, and the network effects built on top of it won’t evaporate the
way they do when a single company burns out.&lt;/p&gt;&lt;p&gt;Email works the same way. Nobody owns email. You can run your own mail server
(people do, I’ve done it, it’s a pain but it works) and you can send mail to
anyone on any provider. The protocol is the platform. Everything else is just a
client.&lt;/p&gt;&lt;h2&gt;What’s missing&lt;/h2&gt;&lt;p&gt;Git forges. That’s the obvious one.&lt;/p&gt;&lt;p&gt;GitHub has been the default for so long that we forget it doesn’t have to be.
Git itself is decentralized. Every clone is a full copy of the repository. But
issues, pull requests, code review, CI configuration, releases, wiki pages. None
of that is in the repo. It’s in GitHub’s database. When GitHub goes down, your
code is fine but your workflow is gone. When GitHub makes a decision you hate,
you can move your repos but you can’t move your issues. The community is stuck
to the platform.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://forgefed.org&quot;&gt;ForgeFed&lt;/a&gt; is an attempt to fix this. It’s an ActivityPub
extension that defines vocabulary for repositories, commits, patches, issues,
merge requests. The idea is that your forge, wherever it’s hosted, can interact
with any other forge that speaks ForgeFed. You open an issue against a repo on
someone else’s server without creating an account there. You submit a patch from
your self-hosted Gitea instance to a project on a friend’s Forgejo instance.
Same protocol, different UIs, same as the fediverse already does for social
media.&lt;/p&gt;&lt;p&gt;It’s early. &lt;a href=&quot;https://vervis.se/&quot;&gt;Vervis&lt;/a&gt; is the reference implementation, mostly
a testbed for the protocol. &lt;a href=&quot;https://forgejo.org/&quot;&gt;Forgejo&lt;/a&gt; is working on
federation support, but the
&lt;a href=&quot;https://codeberg.org/forgejo/discussions/issues/208&quot;&gt;discussion about enabling it on Codeberg&lt;/a&gt;
gives you a sense of where things actually stand: the features are real enough
that Codeberg’s team is planning the rollout, but they’re also worried about
scaling surprises, DDoS amplification, and moderation gaps. The largest Forgejo
instance in the world can’t just flip a switch and see what happens. Pagure had
a plugin that’s unmaintained now. Nobody would call this production-ready. But
the direction is right, and the pieces exist.&lt;/p&gt;&lt;h2&gt;Why now&lt;/h2&gt;&lt;p&gt;AI makes protocols cheap again.&lt;/p&gt;&lt;p&gt;Here’s what I mean. Writing a protocol is not the expensive part. An RFC is a
text file. The expensive part is iterating on it. You write a spec, someone
implements it, they find ambiguities, you revise the spec, someone else
implements the revision, they interpret it differently, you add clarifications,
and on and on. Each cycle costs developer time. The longer the cycles, the
slower the protocol converges. And the slower it converges, the less likely
anyone is to bet on it.&lt;/p&gt;&lt;p&gt;AI changes the economics of this. If an LLM can draft a reference implementation
in an afternoon, you can test a protocol revision against real code the same day
you write the spec change. You can have multiple implementations in different
languages from the same spec, each revealing different assumptions. You can
generate compliance tests, edge case tests, interop tests. The feedback loop
shrinks from months to hours.&lt;/p&gt;&lt;p&gt;This doesn’t replace the human work of protocol design. Trade-offs still need
judgment. But it removes the biggest bottleneck: the gap between “here’s the
spec” and “here’s code that proves it works.”&lt;/p&gt;&lt;h2&gt;Beyond protocols: UX contracts&lt;/h2&gt;&lt;p&gt;There’s a further step I keep thinking about. Protocols define data formats and
message flows. They don’t define what the user sees. Two ActivityPub
implementations can have wildly different UIs, and they do, and that’s fine for
power users who know what they’re signing up for. But for federation to reach
people who don’t want to think about protocols, you need something more.&lt;/p&gt;&lt;p&gt;Call it a UX contract. Not a wireframe spec, not a design system. Something
lighter. A set of guarantees about what the user can expect. If you open a merge
request on a federated forge, the interface for reviewing it should be
recognizable. The terminology should be consistent. The notification flow should
feel familiar. Not identical. Just not alien.&lt;/p&gt;&lt;p&gt;This is harder than a protocol spec. UX is squishy. But AI makes squishy things
cheaper too. You can generate UI scaffolding from a UX contract the same way you
generate network code from a protocol spec. You can test whether different
implementations feel coherent to a user. You can iterate on the contract without
waiting for a design team to hand off mockups to an engineering team.&lt;/p&gt;&lt;p&gt;I don’t think we’re there yet. But the path is visible. Protocols define what
the machines say to each other. UX contracts define what the humans expect to
see. Both need to be cheap to iterate on, and both are getting cheaper.&lt;/p&gt;&lt;h2&gt;The bet&lt;/h2&gt;&lt;p&gt;Centralized platforms win on convenience. Self-hosting wins on control.
Federation splits the difference: you get control over your own data and your
own instance, but you’re not isolated. You’re still in the network.&lt;/p&gt;&lt;p&gt;The fediverse proved this works for social media. It’s not perfect. Moderation
at scale is unsolved, instance discovery is clumsy, the UX is uneven. But it
works, and it’s growing, and nobody can shut it down.&lt;/p&gt;&lt;p&gt;Git forges are next. ForgeFed might not be the thing. Something else might
emerge. But the pattern is set. Protocols over platforms. Federation over
centralization. And with AI making the iteration loop tighter, we’re going to
see protocols ship faster than they ever have before.&lt;/p&gt;&lt;p&gt;I want my code, my issues, my review history, and my community to outlast
whatever forge I’m using. Same way my email outlasts whatever client I’m using.
Same way my social graph outlasts whatever Mastodon instance I’m on. The
protocol is the platform. Everything else is just a client.&lt;/p&gt;</content:encoded>
</item>
<item>
<title>wp2shell: Pre Authentication RCE in WordPress Core</title>
<link>https://wp2shell.com/</link>
<guid isPermaLink="false">Hn4TpBifFQ8sriTD949_MvB3xLkExQzujjc5AA==</guid>
<pubDate>Sun, 19 Jul 2026 04:21:14 +0000</pubDate>
<description>Check whether your WordPress site is affected by the pre-authentication RCE in WordPress Core. A free checker from Searchlight Cyber.</description>
<content:encoded>&lt;div&gt;&lt;a href=&quot;https://slcyber.io&quot;&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;wp&lt;span&gt;2&lt;/span&gt;shell&lt;/div&gt;&lt;h1&gt;Pre Authentication RCE in WordPress Core&lt;/h1&gt;&lt;p&gt;
      Searchlight Cyber&amp;#39;s security research team has discovered a
      &lt;a href=&quot;https://slcyber.io/research-center/wp2shell-pre-authentication-rce-in-wordpress-core&quot;&gt;pre-authentication RCE in WordPress Core&lt;/a&gt;.
      The attack has no preconditions and can be exploited by an anonymous user in a stock
      install of WordPress with no plugins.
    &lt;/p&gt;&lt;p&gt;
      It is estimated that over 500 million websites use WordPress. Given the severity of
      the bug and to give defenders time to patch, we are not releasing technical details
      at this time. We are, however, releasing a checker so you can determine whether your
      instance is vulnerable. Check your site directly below.
    &lt;/p&gt;&lt;h2&gt;Versions Affected&lt;/h2&gt;&lt;table&gt;
      &lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;&amp;lt; 6.9.0&lt;/code&gt;&lt;/td&gt;&lt;td&gt;not affected&lt;/td&gt;&lt;/tr&gt;
      &lt;tr&gt;&lt;td&gt;&lt;code&gt;6.9.0 - 6.9.4&lt;/code&gt;&lt;/td&gt;&lt;td&gt;affected, fixed in 6.9.5&lt;/td&gt;&lt;/tr&gt;
      &lt;tr&gt;&lt;td&gt;&lt;code&gt;7.0.0 - 7.0.1&lt;/code&gt;&lt;/td&gt;&lt;td&gt;affected, fixed in 7.0.2&lt;/td&gt;&lt;/tr&gt;
    &lt;/tbody&gt;&lt;/table&gt;&lt;h2&gt;Mitigation&lt;/h2&gt;&lt;p&gt;
      The best way to protect yourself is to update WordPress immediately. WordPress 7.0.2
      contains the fix (or 6.9.5 if you are on the 6.9 branch). Until you can update, there
      are multiple mitigation options:
    &lt;/p&gt;&lt;ol&gt;
      &lt;li&gt;
        Install the plugin &lt;strong&gt;Disable WP REST API&lt;/strong&gt; to disable unauthenticated
        users from using the WordPress API. This is the easiest option but does have a possible
        low risk of breaking existing functionality.
      &lt;/li&gt;
      &lt;li&gt;
        Use a WAF to block the path &lt;code&gt;/wp-json/batch/v1&lt;/code&gt; and the query parameter
        &lt;code&gt;rest_route=/batch/v1&lt;/code&gt; (both of these patterns must be blocked to secure
        your instance).
      &lt;/li&gt;
      &lt;li&gt;
        Drop the following custom plugin into
        &lt;code&gt;wp-content/plugins/disable-batch-api-for-unauth.php&lt;/code&gt; on the filesystem for
        your WordPress instance via SSH or FTP and enable it from &lt;strong&gt;Plugins&lt;/strong&gt; in
        the admin panel:
        &lt;pre&gt;&lt;code&gt;&amp;lt;?php
/**
 * Plugin Name: Disable Unauthenticated REST Batch API
 * Description: Requires an authenticated WordPress user for REST batch requests.
 * Version: 1.0.0
 * Requires at least: 5.6
 * License: GPL-2.0-or-later
 */

defined( &amp;#39;ABSPATH&amp;#39; ) || exit;

/**
 * Reject anonymous requests to the core REST batch endpoint.
 *
 * @param mixed           $result  Pre-calculated dispatch result.
 * @param WP_REST_Server  $server  REST server instance.
 * @param WP_REST_Request $request Current REST request.
 * @return mixed|WP_Error
 */
function wporg_require_authentication_for_rest_batch( $result, $server, $request ) {
    if ( &amp;#39;/batch/v1&amp;#39; !== strtolower( untrailingslashit( $request-&amp;gt;get_route() ) ) || is_user_logged_in() ) {
        return $result;
    }

    return new WP_Error(
        &amp;#39;rest_batch_authentication_required&amp;#39;,
        &amp;#39;Authentication is required to use the batch API.&amp;#39;,
        array( &amp;#39;status&amp;#39; =&amp;gt; 401 )
    );
}

add_filter( &amp;#39;rest_pre_dispatch&amp;#39;, &amp;#39;wporg_require_authentication_for_rest_batch&amp;#39;, -1000, 3 );&lt;/code&gt;&lt;/pre&gt;
      &lt;/li&gt;
    &lt;/ol&gt;&lt;p&gt;
      Note that these solutions may have impact on legitimate use of the site and should only
      be considered emergency temporary measures until you can update.
    &lt;/p&gt;&lt;h2&gt;Credits&lt;/h2&gt;&lt;p&gt;
      Discovered and authored by
      &lt;a href=&quot;https://x.com/hash_kitten&quot;&gt;Adam Kues&lt;/a&gt;
      of &lt;a href=&quot;https://slcyber.io/&quot;&gt;Searchlight Cyber&lt;/a&gt;.
    &lt;/p&gt;</content:encoded>
</item>
<item>
<title>Hardcore IndieWeb: Run your own website 100% independently for only $0.01/day</title>
<link>https://www.neatnik.net/hardcore-indieweb</link>
<guid isPermaLink="false">zaBU_p1YPqN9HIdCUEHFuZVMC-PWLFPoWbo1PA==</guid>
<pubDate>Sun, 19 Jul 2026 04:21:14 +0000</pubDate>
<description>Hardcore IndieWeb: Run your own website 100% independently for only $0.01/day</description>
<content:encoded>&lt;h1&gt;Hardcore IndieWeb: Run your own website 100% independently for only $0.01/day&lt;/h1&gt;&lt;p&gt;&lt;small&gt; &lt;a href=&quot;https://adam.omg.lol&quot;&gt;Adam Newbold&lt;/a&gt;&lt;br/&gt; July 18, 2026&lt;/small&gt;&lt;/p&gt;&lt;p&gt;If you’re not familiar with the &lt;a href=&quot;https://indieweb.org&quot;&gt;IndieWeb&lt;/a&gt; movement, it’s a practical approach to maintaining your presence on the web that emphasizes fully owning your identity and your content. This is especially relevant now, at a time when a growing number of corporations want to own and control those things for you. The IndieWeb ethos seeks to eliminate external influence and control over both you and your stuff.&lt;/p&gt;&lt;p&gt;And as with any movement, there are a bunch of different services out there aiming to help you plug into this modern independent web. &lt;a href=&quot;https://home.omg.lol&quot;&gt;I even run one myself!&lt;/a&gt; But I always place my values over profit, so I don’t have any problem sharing the simple truth that &lt;strong&gt;you don’t need a fancy subscription blogging service to join the IndieWeb&lt;/strong&gt;. In fact, when you use one of those services, you’re actually &lt;em&gt;taking a step back&lt;/em&gt; from real independence, because your content often winds up living in someone else’s database on someone else’s server. They’ll tell you that’s OK because you can always export it whenever you’d like, in open formats, and of course that’s nice. But you still don’t fully fully own and control your content when you’re dependent on someone else’s service.&lt;/p&gt;&lt;p&gt;There’s nothing wrong with using those services, to be clear. And if you’re already using them and happy with them, that’s great! This guide isn’t for you. But if the idea of having &lt;em&gt;complete&lt;/em&gt; independence and control over your content on the web is appealing to you, read on. Because that’s what &lt;em&gt;Hardcore IndieWeb&lt;/em&gt; is all about.&lt;/p&gt;&lt;h2&gt;Defining &lt;em&gt;Hardcore IndieWeb&lt;/em&gt;&lt;/h2&gt;&lt;p&gt;&lt;em&gt;Hardcore IndieWeb&lt;/em&gt; fully embraces existing IndieWeb principles; there’s really nothing new or different there. The key aspects of &lt;em&gt;Hardcore IndieWeb&lt;/em&gt; are &lt;strong&gt;control&lt;/strong&gt; over your content and &lt;strong&gt;portability&lt;/strong&gt; of your website:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;If your content doesn’t primarily exist on your own hard drive, you don’t fully control your content.&lt;/li&gt;
&lt;li&gt;If you don’t have a copy of your published HTML and web assets on your own hard drive, it’s not truly portable.&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;Why does this matter? Consider what happens if the IndieWeb blogging service you’re using goes belly-up one day. Your ability to move to a new service depends entirely on your ability to export your data. What if you can’t export it? The service provider may have told you that you own your content, but what good does that do if you can’t access it?&lt;/p&gt;&lt;p&gt;From a portability standpoint, consider what happens if you discover that the owner of your IndieWeb blogging service has been outed as engaging in abhorrent behavior or just comes down with a major case of the &lt;em&gt;ick&lt;/em&gt; (this has happened before!). Sure, you can export your data in an open format and move house. But now you have to find another service that can work with that format, or convert everything and adopt an entirely new set of tools or processes. And you have to &lt;em&gt;move house&lt;/em&gt;, which is a major pain.&lt;/p&gt;&lt;p&gt;If you follow &lt;em&gt;Hardcore IndieWeb&lt;/em&gt;, neither of these situations can affect you. Your content remains completely under your own control at all times, and your website exists in a fully published format at all times. The independence is tangible, and you can benefit from it whenever necessary.&lt;/p&gt;&lt;h2&gt;The methodology&lt;/h2&gt;&lt;p&gt;In many ways—no, actually, in &lt;em&gt;every&lt;/em&gt; way—&lt;em&gt;Hardcore IndieWeb&lt;/em&gt; mirrors the very same web publishing practices that were used in the 90s when the web was brand new. These were things that I did back then myself, every day, and they worked great! And they still work today, of course, but few people would recognize the approach because we’re now multiple levels deep in hybrid SaaS CMS/SSG solutions tethered to half a dozen markup languages and twice as many template systems. We’re living in a messy and complex web right now, but it doesn’t have to be that way. The complexity is one choice, and the simplicity of &lt;em&gt;Hardcore IndieWeb&lt;/em&gt; is a different choice.&lt;/p&gt;&lt;p&gt;The process looks like this:&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;Author your web content on your hard drive.&lt;/li&gt;
&lt;li&gt;Preview it in your web browser.&lt;/li&gt;
&lt;li&gt;When you’re happy with what you have, upload it to your web host. Repeat as often as you’d like.&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;That’s it. It really is that simple. Besides a domain name, here’s what you’ll need:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;A text editor&lt;/li&gt;
&lt;li&gt;A file transfer tool&lt;/li&gt;
&lt;li&gt;A web host&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;Again, &lt;em&gt;that’s all&lt;/em&gt;. You don’t need any other special software. No programming environments or IDEs, no frameworks or shells or CLI tools. And &lt;em&gt;definitely&lt;/em&gt; no monthly subscriptions. In the next section, I’ll cover choosing these items, and I’ll suggest a very nice web host that literally only costs $0.01/day, which you can pay-as-you-go (so no subscription).&lt;/p&gt;&lt;p&gt;(You do also need to know some HTML, but &lt;a href=&quot;https://htmlforpeople.com&quot;&gt;everyone can learn HTML&lt;/a&gt;. And in reality, you can get away with only knowing a few tags and largely copying and pasting, so don’t sweat it!)&lt;/p&gt;&lt;h2&gt;Getting Started&lt;/h2&gt;&lt;p&gt;First, you’ll need to find a text editor that you like. Hopefully you already have one, but if not, you can try &lt;a href=&quot;https://en.wikipedia.org/wiki/List_of_text_editors&quot;&gt;different editors&lt;/a&gt; until you find one that works for you. (&lt;a href=&quot;https://usesthis.com/interviews/adam.newbold/&quot;&gt;I use Nova by Panic&lt;/a&gt;, myself.) You can use virtually any editor, because it only needs to support saving files to your disk (and they all do that).&lt;/p&gt;&lt;p&gt;Then, you’ll need a way to transfer files. Part of the reason I use Nova as my editor is that it handles file transfers, too. But there are plenty of options out there. &lt;a href=&quot;https://filezilla-project.org&quot;&gt;FileZilla&lt;/a&gt; is a nice cross-platform app. You can use anything that can transfer files over SSH (or the SFTP protocol).&lt;/p&gt;&lt;p&gt;Finally, you’ll need a place to host your web content. There are a zillion web hosts out there, all with various features and benefits. But for &lt;em&gt;Hardcore IndieWeb&lt;/em&gt;, you only need the basics, and you don’t need to spend a lot of money at all. For just $0.01/day, you can run a static website at &lt;a href=&quot;https://www.nearlyfreespeech.net&quot;&gt;NearlyFreeSpeech.net&lt;/a&gt;, and that’s what I recommend. I’ve had an active account with them since 2008 and they’re awesome!&lt;/p&gt;&lt;p&gt;You can use any web host you’d like, though. If you want to use NearlyFreeSpeech, go ahead and sign up, fund your account (you can kick things off with as little as $0.25), add a site (static, non-production), and you’re good to go. You’ll find your site’s login information (for file transfers) on the Sites tab after clicking on your site name. You’ll get a free subdomain to work with, but you can add your own personal domain on the Domains tab if you’d like (and you should totally do that).&lt;/p&gt;&lt;h2&gt;The Process&lt;/h2&gt;&lt;p&gt;Once you have your editor, a way to transfer files, and an active account with a web host, you’re ready to get going. At this point, the world is your oyster. You can run any kind of website you’d like, but for this guide, we’ll assume you want to run a blog.&lt;/p&gt;&lt;p&gt;Most of the time, a blog consists of these things:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;A landing page&lt;/li&gt;
&lt;li&gt;Posts/articles&lt;/li&gt;
&lt;li&gt;An archive page&lt;/li&gt;
&lt;li&gt;A feed&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;If you’ve always assumed that you need a special blogging service or software to maintain these items, that’s totally understandable. But you don’t! You can actually manage these yourself, and it’s really super easy. At this point you might (understandably) be wondering “what kind of person manages their own RSS feed in a text editor?” And the answer is &lt;strong&gt;you&lt;/strong&gt;. You are the kind of person who is totally capable of managing your own RSS feed in your own editor.&lt;/p&gt;&lt;p&gt;We’ll cover that shortly. First, let’s cover the HTML pages.&lt;/p&gt;&lt;h3&gt;A Quick Note About Web Pages&lt;/h3&gt;&lt;p&gt;If you already have your existing web site or blog in HTML format, you’re in a great position to get started with &lt;em&gt;Hardcore IndieWeb&lt;/em&gt;. If you don’t, that’s OK. Depending on where your blog lives today, there are likely ways to export it in HTML or to convert it into HTML. If your blog is huge, you’ll probably want to use a tool to make this easier. If it’s small, maybe you’d enjoy diving into creating your new HTML files yourself (which is always a fun way to revisit your posts and such).&lt;/p&gt;&lt;p&gt;If you don’t know much HTML, &lt;a href=&quot;https://htmlforpeople.com&quot;&gt;you can totally learn it&lt;/a&gt;. Heck, I’ll even teach you if you’d like. It’s super fun to learn, and using HTML brings you so much closer to your web content in ways that modern tools just don’t capture. Markdown may be many people’s preferred way to author content, but HTML is the language of the web, and I can personally attest to the fact that there are plenty of occasions where doing something in plain HTML turned out to be far easier than fighting a Markdown parser.&lt;/p&gt;&lt;p&gt;Anyway. If you want to jump in with some ready-made web material to use as a basis for your site, there are a bajillion free web designs and templates out there. &lt;a href=&quot;https://html5up.net&quot;&gt;Here’s one I just found&lt;/a&gt;. You can download something you like, open it in your text editor, and make it your very own.&lt;/p&gt;&lt;h3&gt;Landing Page&lt;/h3&gt;&lt;p&gt;Your landing page is your home on the web, and you can do whatever you’d like with it. It can show your latest blog post, or several of them, or none of them at all. It can show entire posts or just the first couple of sentences. You’re in complete control here, and you can put whatever you want on that page. If you want to display your latest blog post, just copy and paste it there and add a link to its standalone page. If you want to show your five most recent posts, paste the new one at the top and delete the oldest one at the bottom. You’re now working directly with your content on your own landing page, and you can do whatever you’d like, however you’d like.&lt;/p&gt;&lt;p&gt;This might sound like madness, if you’re fully accustomed to today’s modern template-driven systems for managing blogs and web content. And that’s a totally fair way to feel about it. But consider that this approach offers some unique possibilities that are actually harder to do with template-driven solutions. With this approach, you’re in a position to deviate from the norm. You can take things in different directions. You can apply your creativity in fun and interesting ways, and you’ll never find yourself fighting your CMS/SSG/templating engine/etc. because you’re not using one. It is utterly freeing.&lt;/p&gt;&lt;p&gt;Your landing page should be called &lt;code&gt;index.html&lt;/code&gt; and should sit in the root level of your web root (which is &lt;code&gt;/home/public&lt;/code&gt; if you’re using NearlyFreeSpeech).&lt;/p&gt;&lt;h3&gt;Blog Posts&lt;/h3&gt;&lt;p&gt;Blog posts are just web pages. These are super easy to maintain and publish. You can run with any process you’d like, but the simplest approach is to just make a copy of the web page for your last blog post, give it a unique file name, and then edit it with your new blog post content.&lt;/p&gt;&lt;p&gt;This is a good time to think about your site structure and how you want to organize your files. The URLs to your posts will reflect the file structure on the disk. Do you want all of your blog posts to be in a &lt;code&gt;/blog/&lt;/code&gt; section? Then make a &lt;code&gt;blog&lt;/code&gt; folder in your web root and store your blog files there. You can name your posts with the desired slug, e.g. &lt;code&gt;the-best-lunch-i-ever-had.html&lt;/code&gt;, or you could make folders for them and store the blog posts inside each folder as an &lt;code&gt;index.html&lt;/code&gt; file to hide the &lt;code&gt;.html&lt;/code&gt; file extension (but contrary to popular belief, it’s a beautiful and honest file extension).&lt;/p&gt;&lt;p&gt;This is also a good time to consider another benefit of &lt;em&gt;Hardcore IndieWeb&lt;/em&gt;. When your posts are individual HTML files (not Markdown files or database entries), they can finally be seen as the individual web pages that they truly are. And that means that you can really lean into making all of your posts unique! They don’t all have to be cookie-cutter paper-doll clones of one another; that’s just a bi-product of modern web publishing tools and the cultural influence that they have on our concept of a “blog”. You can now go ahead and make every blog post as special and individual as you’d like. Each post can have its own personality, baked right into its HTML. Individual style, individual appearance, even individual layout. Literally everything is possible with this approach.&lt;/p&gt;&lt;h3&gt;Archive Page&lt;/h3&gt;&lt;p&gt;Managing your archive page is simple. Make a folder called &lt;code&gt;archive&lt;/code&gt; (or whatever you’d like to call it), add an &lt;code&gt;index.html&lt;/code&gt; page there, and list your blog posts on the page. You can organize them in any way you wish. You can highlight your favorite posts at the top of the page. You can do anything—it’s your archive page to manage as you please.&lt;/p&gt;&lt;h3&gt;Feed&lt;/h3&gt;&lt;p&gt;An RSS feed isn’t some magical esoteric web code thing; it’s just a file on disk. And it’s a file that is in a fairly simple format to parse and work with, and you can totally do that in your text editor. It’s really not difficult.&lt;/p&gt;&lt;p&gt;You can get started with a simple Atom feed (100% RSS-compatible and universally supported) by visiting the &lt;a href=&quot;https://en.wikipedia.org/wiki/Atom_(web_standard)&quot;&gt;Atom feed page on Wikipedia&lt;/a&gt;, copying the example feed shown halfway down the page, and pasting that into a &lt;code&gt;feed.xml&lt;/code&gt; file (or whatever you want to call it). Then just change all of the &lt;code&gt;example.com&lt;/code&gt; references and &lt;code&gt;&amp;lt;title&amp;gt;, &amp;lt;subtitle&amp;gt;&lt;/code&gt;, etc. values to reflect your own domain and information. Finally, just include an &lt;code&gt;&amp;lt;entry&amp;gt;&lt;/code&gt; section for each post that you want to include in your feed. Set the relevant data within each (dates/times, title, summary, etc.). For the &lt;code&gt;&amp;lt;id&amp;gt;&lt;/code&gt;, just &lt;a href=&quot;https://www.uuidgenerator.net&quot;&gt;grab a fresh UUID&lt;/a&gt;. And when you’re finished, copy and paste your feed into the &lt;a href=&quot;https://validator.w3.org/feed/#validate_by_input&quot;&gt;W3C Feed Validation Service&lt;/a&gt; to make sure that it can be parsed. If there are any issues, the validator will tell you what needs to be corrected.&lt;/p&gt;&lt;h3&gt;Uploading&lt;/h3&gt;&lt;p&gt;This is the easiest part! When you’re ready to publish your website, just use your file transfer program to connect to the server and drag your files into the remote server. The first time you publish to your web host, you’ll want to copy everything over. But on subsequent uploads, you can just transfer anything that’s new or changed (e.g. your landing page, new blog post, feed, and archive page).&lt;/p&gt;&lt;h2&gt;Next Steps&lt;/h2&gt;&lt;p&gt;At this point, you have a fully functioning website that lives primarily on your own computer and is completely portable. This is the &lt;em&gt;Hardcore IndieWeb&lt;/em&gt; way. You’re ready for anything: if your host vanished, you can just use another host and upload your website there. You don’t have to worry about critical security vulnerabilities in blogging software or SSG dependencies or anything like that. You are in complete control over every aspect of your web content, and you’re reliant upon no one else.&lt;/p&gt;&lt;p&gt;Where do you go from here? That’s up to you. You don’t have to go anywhere at all. You can follow this exact process forever and you’ll always have a wonderful website that is entirely your own and completely independent.&lt;/p&gt;&lt;p&gt;You could also choose to explore additional tools and processes to support your own workflow. Keep in mind that everything you add creates a dependency on something else, but as long as your content continues to live primarily on your own device and you have a full copy of your ready-to-publish website, you’re A-OK as far as &lt;em&gt;Hardcore IndieWeb&lt;/em&gt; goes.&lt;/p&gt;&lt;h2&gt;Conclusion&lt;/h2&gt;&lt;p&gt;&lt;em&gt;“Did I just read 2,500 words that tell me to write my own HTML and upload it to a web server?”&lt;/em&gt; Yeah. You did.&lt;/p&gt;&lt;p&gt;If this seems like ridiculous advice in 2026, I think that’s largely the result of how we’ve strayed so far from the original simplicity we had when working with the web. The layers of technical complexity introduced over the past 30 years, the new additive processes, and the expectations that come with it all combine into a modern approach that turns over more of our control and independence to other people than at any other point in the web’s history. Even under the “IndieWeb” umbrella, so many people have their only copy of their entire web presence entrusted to some guy on the internet who’s promised to take care of it. That’s not &lt;em&gt;real&lt;/em&gt; independence, and we should stop pretending that it is.&lt;/p&gt;&lt;p&gt;&lt;em&gt;Hardcore IndieWeb&lt;/em&gt; may not be for everyone. But if you care about your words, who holds them, and where and how they appear online, then you might just find that it’s for &lt;em&gt;you&lt;/em&gt;. And if while embracing this particular approach you happen to reconnect with the web in a way that so many of us enjoyed early on, experiencing the magic and delight of working directly with HTML and copying files to your own space on a web host, then that’s a heck of an added bonus. &lt;em&gt;Hardcore IndieWeb&lt;/em&gt; may sound intense, but the reality is that it’s empowering, fun, and rewarding. And you deserve that.&lt;/p&gt;&lt;figure&gt;

&lt;p&gt;&lt;strong&gt;Need help with any of this? Or just want to join a super nice community of web enthusiasts?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Consider joining &lt;a href=&quot;https://home.omg.lol&quot;&gt;omg.lol&lt;/a&gt;! You’ll feel right at home with our active community of folks who would &lt;em&gt;love&lt;/em&gt; to chat about (or even help with) any aspect of &lt;em&gt;Hardcore IndieWeb&lt;/em&gt;.&lt;/p&gt;
&lt;/figure&gt;</content:encoded>
</item>
<item>
<title>Using AI To Build A Website: Security &amp; HIPAA Risks</title>
<link>https://leadtorecovery.com/blog/using-ai-to-build-a-website/</link>
<enclosure type="image/jpeg" length="0" url="https://leadtorecovery.com/wp-content/uploads/ai-websites.webp"></enclosure>
<guid isPermaLink="false">4MZlpG2L183jZXYg8UsysxpFiLF1FQQYmr15uQ==</guid>
<pubDate>Sat, 18 Jul 2026 08:23:39 +0000</pubDate>
<description>Can you safely protect patient data with automated code? Explore why using AI to build a website introduces critical security, privacy, and HIPAA compliance risks.</description>
<content:encoded>&lt;h2&gt;Using AI to Build a Website Can Create More Than Design Problems&lt;/h2&gt;&lt;p&gt;AI website builders are popular because they remove friction. You can describe what you want in plain language, choose a design direction, adjust a few sections, connect a custom domain, and launch a complete website without coding experience.&lt;/p&gt;&lt;p&gt;For a simple personal site, that may be enough. For your healthcare or behavioral health organization, the stakes are higher. Your website needs to support trust, protect user data, guide people to the right next step, perform well in search engines, and connect cleanly with your intake and marketing systems.&lt;/p&gt;&lt;p&gt;That is where AI-generated sites often fall short. Many AI powered website builders focus on speed and convenience. What they do not always provide is production-ready code, secure form architecture, HIPAA-aware workflows, clean WordPress compatibility, or long-term technical ownership.&lt;/p&gt;&lt;h2&gt;The Hidden Costs of Using AI to Build a Website&lt;/h2&gt;&lt;p&gt;The hidden costs of an AI generated website usually appear after launch. At first, your site may look modern, mobile friendly, and easy to use. Then your team starts editing pages, adding plugins, improving SEO, installing tracking, or changing forms.&lt;/p&gt;&lt;p&gt;That is when the problems surface.&lt;/p&gt;&lt;p&gt;Common long-term risks include:&lt;/p&gt;&lt;ul&gt; &lt;li&gt;Technical debt that slows the site down&lt;/li&gt; &lt;li&gt;Platform conflicts that make updates difficult&lt;/li&gt; &lt;li&gt;Security risks tied to forms and user input&lt;/li&gt; &lt;li&gt;Maintenance costs that erase the upfront savings&lt;/li&gt; &lt;li&gt;Limited scalability as your organization grows&lt;/li&gt; &lt;/ul&gt;&lt;p&gt;A free AI website builder or basic plan may seem cost-effective in the beginning. But if your site later needs a rebuild, security cleanup, SEO repair, or custom development, the real cost can become much higher than expected.&lt;/p&gt;&lt;h2&gt;Code Quality and AI-Generated Technical Debt&lt;/h2&gt;&lt;p&gt;AI tools can generate functional snippets quickly, but functional does not always mean clean, efficient, or maintainable.&lt;/p&gt;&lt;p&gt;A skilled developer considers your design system, page templates, integrations, performance goals, tracking needs, and future updates. An AI builder reacts to prompts. It may create something that works in the moment without understanding how that code will affect your entire website later.&lt;/p&gt;&lt;h3&gt;Bloated Code Can Slow Performance and Hurt Core Web Vitals&lt;/h3&gt;&lt;p&gt;AI generated code often uses more code than necessary. It may load full libraries for small effects, duplicate functions, add excessive wrappers, or create messy CSS that becomes harder to manage over time.&lt;/p&gt;&lt;p&gt;That can lead to slower load times, poor Core Web Vitals, higher hosting resource usage, weaker SEO performance, and more difficult technical audits.&lt;/p&gt;&lt;p&gt;For your treatment center website, speed is not just a technical concern. A slow website can reduce trust and increase the chance that a high-intent visitor leaves before calling, submitting a form, or reading about your services. &lt;a href=&quot;https://developers.google.com/search/docs/appearance/core-web-vitals&quot;&gt;Google Search Central’s Core Web Vitals guidance&lt;/a&gt; explains that these metrics measure real-world user experience for loading performance, interactivity, and visual stability.&lt;/p&gt;&lt;h3&gt;AI-Generated Snippets Often Lack Architectural Planning&lt;/h3&gt;&lt;p&gt;AI can create a section, form, animation, or landing page. What it often lacks is a clear architectural strategy.&lt;/p&gt;&lt;p&gt;That can create inconsistent naming, repeated code, mixed design patterns, poor separation between content and functionality, and pages that are difficult to scale or hand off.&lt;/p&gt;&lt;p&gt;This becomes especially risky when your site needs location pages, program pages, service hubs, blog templates, campaign landing pages, or intake-related workflows.&lt;/p&gt;&lt;h3&gt;Cookie-Cutter Layouts Can Weaken Trust&lt;/h3&gt;&lt;p&gt;Many website builders and AI website generators rely on similar template structures. Even when the colors, images, and copy are different, the final site can still feel generic.&lt;/p&gt;&lt;p&gt;That matters in behavioral health. Your visitors may be comparing treatment options during an emotional, high-stakes moment. If your site looks like every other AI built website, it may not communicate the trust, clinical seriousness, or brand differentiation your organization needs.&lt;/p&gt;</content:encoded>
</item>
<item>
<title>Professional Website Essentials for New Businesses</title>
<link>https://coruzant.com/web/professional-website-to-attract-more-customers/</link>
<enclosure type="image/jpeg" length="0" url="https://coruzant.com/wp-content/uploads/2026/07/calendar-wall-website-layout-ux-design-futuristic-design-2.jpg"></enclosure>
<guid isPermaLink="false">Uc-XB0dhc1FFc3rFtbqW6nOgd-dK2XI4wezprA==</guid>
<pubDate>Sat, 18 Jul 2026 02:03:05 +0000</pubDate>
<description>Learn why a professional website is essential for attracting customers and establishing your brand&#39;s online presence.</description>
<content:encoded>&lt;div&gt;
&lt;div&gt;&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&lt;div&gt;&lt;div&gt;

&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;div&gt;
&lt;div&gt;&lt;h3&gt;Finance&lt;/h3&gt;&lt;/div&gt; &lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;

&lt;div&gt;
        &lt;div&gt;
            &lt;div&gt;
                                    &lt;div&gt;
                                                &lt;div&gt;&lt;a href=&quot;https://coruzant.com/mobile/mobile-app-design-what-makes-an-app-easy/&quot;&gt;&lt;/a&gt;&lt;/div&gt;                                                                    &lt;/div&gt;
                
                &lt;div&gt;
                                        
                    &lt;h3&gt;&lt;a href=&quot;https://coruzant.com/mobile/mobile-app-design-what-makes-an-app-easy/&quot;&gt;Mobile App Design: What Makes an App Easy to Use and Worth Keeping&lt;/a&gt;&lt;/h3&gt;
                    
                                            &lt;div&gt;
                                                        
                                                            &lt;span&gt;
                                                                                                                                                                                    &lt;span&gt;&lt;a href=&quot;https://coruzant.com/mobile/mobile-app-design-what-makes-an-app-easy/#respond&quot;&gt;0&lt;/a&gt;&lt;/span&gt;                                &lt;/span&gt;
                                                    &lt;/div&gt;
                    
                    &lt;div&gt;
A new mobile app usually gets one honest shot....&lt;/div&gt;
                    
                                    &lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;

        
        &lt;div&gt;
            &lt;div&gt;
                                    &lt;div&gt;
                                                &lt;div&gt;&lt;a href=&quot;https://coruzant.com/blockchain/web3-development-starts-with-saying-no/&quot;&gt;&lt;/a&gt;&lt;/div&gt;                                                                    &lt;/div&gt;
                
                &lt;div&gt;
                                        
                    &lt;h3&gt;&lt;a href=&quot;https://coruzant.com/blockchain/web3-development-starts-with-saying-no/&quot;&gt;Why Serious Web3 Development Starts With Saying “No”&lt;/a&gt;&lt;/h3&gt;
                    
                                            &lt;div&gt;
                                                        
                                                            &lt;span&gt;
                                                                                                                                                                                    &lt;span&gt;&lt;a href=&quot;https://coruzant.com/blockchain/web3-development-starts-with-saying-no/#respond&quot;&gt;0&lt;/a&gt;&lt;/span&gt;                                &lt;/span&gt;
                                                    &lt;/div&gt;
                    
                    &lt;div&gt;
Most enterprise blockchain pilots end up going nowhere. Teams...&lt;/div&gt;
                    
                                    &lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;

        
        &lt;div&gt;
            &lt;div&gt;
                                    &lt;div&gt;
                                                &lt;div&gt;&lt;a href=&quot;https://coruzant.com/blockchain/crypto-market-making-services-for-tokens/&quot;&gt;&lt;/a&gt;&lt;/div&gt;                                                                    &lt;/div&gt;
                
                &lt;div&gt;
                                        
                    &lt;h3&gt;&lt;a href=&quot;https://coruzant.com/blockchain/crypto-market-making-services-for-tokens/&quot;&gt;Top Crypto Market Making Services for Exchanges and Token Projects&lt;/a&gt;&lt;/h3&gt;
                    
                                            &lt;div&gt;
                                                        
                                                            &lt;span&gt;
                                                                                                                                                                                    &lt;span&gt;&lt;a href=&quot;https://coruzant.com/blockchain/crypto-market-making-services-for-tokens/#respond&quot;&gt;0&lt;/a&gt;&lt;/span&gt;                                &lt;/span&gt;
                                                    &lt;/div&gt;
                    
                    &lt;div&gt;
Liquidity is one of the most important factors in...&lt;/div&gt;
                    
                                    &lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;

        
        &lt;div&gt;
            &lt;div&gt;
                                    &lt;div&gt;
                                                &lt;div&gt;&lt;a href=&quot;https://coruzant.com/digital-strategy/businesses-need-digital-marketing/&quot;&gt;&lt;/a&gt;&lt;/div&gt;                                                                    &lt;/div&gt;
                
                &lt;div&gt;
                                        
                    &lt;h3&gt;&lt;a href=&quot;https://coruzant.com/digital-strategy/businesses-need-digital-marketing/&quot;&gt;Why Growing Businesses Eventually Need a Digital Marketing Agency&lt;/a&gt;&lt;/h3&gt;
                    
                                            &lt;div&gt;
                                                        
                                                            &lt;span&gt;
                                                                                                                                                                                    &lt;span&gt;&lt;a href=&quot;https://coruzant.com/digital-strategy/businesses-need-digital-marketing/#respond&quot;&gt;0&lt;/a&gt;&lt;/span&gt;                                &lt;/span&gt;
                                                    &lt;/div&gt;
                    
                    &lt;div&gt;
Most small businesses start out handling marketing themselves, and...&lt;/div&gt;
                    
                                    &lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;

        
        &lt;div&gt;
            &lt;div&gt;
                                    &lt;div&gt;
                                                &lt;div&gt;&lt;a href=&quot;https://coruzant.com/software/beereaders-review-features-pros-cons-and-alternatives/&quot;&gt;&lt;/a&gt;&lt;/div&gt;                                                                    &lt;/div&gt;
                
                &lt;div&gt;
                                        
                    &lt;h3&gt;&lt;a href=&quot;https://coruzant.com/software/beereaders-review-features-pros-cons-and-alternatives/&quot;&gt;BeeReaders Review: Features, Pros, Cons, and Alternatives&lt;/a&gt;&lt;/h3&gt;
                    
                                            &lt;div&gt;
                                                        
                                                            &lt;span&gt;
                                                                                                                                                                                    &lt;span&gt;&lt;a href=&quot;https://coruzant.com/software/beereaders-review-features-pros-cons-and-alternatives/#respond&quot;&gt;0&lt;/a&gt;&lt;/span&gt;                                &lt;/span&gt;
                                                    &lt;/div&gt;
                    
                    &lt;div&gt;
If you are researching a BeeReaders review, you are...&lt;/div&gt;
                    
                                    &lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;

        &lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&lt;div&gt;&lt;div&gt;

&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;div&gt;
&lt;div&gt;&lt;h3&gt;Marketing&lt;/h3&gt;&lt;/div&gt; &lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;

&lt;div&gt;
        &lt;div&gt;
            &lt;div&gt;
                                    &lt;div&gt;
                                                &lt;div&gt;&lt;a href=&quot;https://coruzant.com/mobile/mobile-app-design-what-makes-an-app-easy/&quot;&gt;&lt;/a&gt;&lt;/div&gt;                                                                    &lt;/div&gt;
                
                &lt;div&gt;
                                        
                    &lt;h3&gt;&lt;a href=&quot;https://coruzant.com/mobile/mobile-app-design-what-makes-an-app-easy/&quot;&gt;Mobile App Design: What Makes an App Easy to Use and Worth Keeping&lt;/a&gt;&lt;/h3&gt;
                    
                                            &lt;div&gt;
                                                        
                                                            &lt;span&gt;
                                                                                                                                                                                    &lt;span&gt;&lt;a href=&quot;https://coruzant.com/mobile/mobile-app-design-what-makes-an-app-easy/#respond&quot;&gt;0&lt;/a&gt;&lt;/span&gt;                                &lt;/span&gt;
                                                    &lt;/div&gt;
                    
                    &lt;div&gt;
A new mobile app usually gets one honest shot....&lt;/div&gt;
                    
                                    &lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;

        
        &lt;div&gt;
            &lt;div&gt;
                                    &lt;div&gt;
                                                &lt;div&gt;&lt;a href=&quot;https://coruzant.com/blockchain/web3-development-starts-with-saying-no/&quot;&gt;&lt;/a&gt;&lt;/div&gt;                                                                    &lt;/div&gt;
                
                &lt;div&gt;
                                        
                    &lt;h3&gt;&lt;a href=&quot;https://coruzant.com/blockchain/web3-development-starts-with-saying-no/&quot;&gt;Why Serious Web3 Development Starts With Saying “No”&lt;/a&gt;&lt;/h3&gt;
                    
                                            &lt;div&gt;
                                                        
                                                            &lt;span&gt;
                                                                                                                                                                                    &lt;span&gt;&lt;a href=&quot;https://coruzant.com/blockchain/web3-development-starts-with-saying-no/#respond&quot;&gt;0&lt;/a&gt;&lt;/span&gt;                                &lt;/span&gt;
                                                    &lt;/div&gt;
                    
                    &lt;div&gt;
Most enterprise blockchain pilots end up going nowhere. Teams...&lt;/div&gt;
                    
                                    &lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;

        
        &lt;div&gt;
            &lt;div&gt;
                                    &lt;div&gt;
                                                &lt;div&gt;&lt;a href=&quot;https://coruzant.com/blockchain/crypto-market-making-services-for-tokens/&quot;&gt;&lt;/a&gt;&lt;/div&gt;                                                                    &lt;/div&gt;
                
                &lt;div&gt;
                                        
                    &lt;h3&gt;&lt;a href=&quot;https://coruzant.com/blockchain/crypto-market-making-services-for-tokens/&quot;&gt;Top Crypto Market Making Services for Exchanges and Token Projects&lt;/a&gt;&lt;/h3&gt;
                    
                                            &lt;div&gt;
                                                        
                                                            &lt;span&gt;
                                                                                                                                                                                    &lt;span&gt;&lt;a href=&quot;https://coruzant.com/blockchain/crypto-market-making-services-for-tokens/#respond&quot;&gt;0&lt;/a&gt;&lt;/span&gt;                                &lt;/span&gt;
                                                    &lt;/div&gt;
                    
                    &lt;div&gt;
Liquidity is one of the most important factors in...&lt;/div&gt;
                    
                                    &lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;

        
        &lt;div&gt;
            &lt;div&gt;
                                    &lt;div&gt;
                                                &lt;div&gt;&lt;a href=&quot;https://coruzant.com/digital-strategy/businesses-need-digital-marketing/&quot;&gt;&lt;/a&gt;&lt;/div&gt;                                                                    &lt;/div&gt;
                
                &lt;div&gt;
                                        
                    &lt;h3&gt;&lt;a href=&quot;https://coruzant.com/digital-strategy/businesses-need-digital-marketing/&quot;&gt;Why Growing Businesses Eventually Need a Digital Marketing Agency&lt;/a&gt;&lt;/h3&gt;
                    
                                            &lt;div&gt;
                                                        
                                                            &lt;span&gt;
                                                                                                                                                                                    &lt;span&gt;&lt;a href=&quot;https://coruzant.com/digital-strategy/businesses-need-digital-marketing/#respond&quot;&gt;0&lt;/a&gt;&lt;/span&gt;                                &lt;/span&gt;
                                                    &lt;/div&gt;
                    
                    &lt;div&gt;
Most small businesses start out handling marketing themselves, and...&lt;/div&gt;
                    
                                    &lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;

        
        &lt;div&gt;
            &lt;div&gt;
                                    &lt;div&gt;
                                                &lt;div&gt;&lt;a href=&quot;https://coruzant.com/software/beereaders-review-features-pros-cons-and-alternatives/&quot;&gt;&lt;/a&gt;&lt;/div&gt;                                                                    &lt;/div&gt;
                
                &lt;div&gt;
                                        
                    &lt;h3&gt;&lt;a href=&quot;https://coruzant.com/software/beereaders-review-features-pros-cons-and-alternatives/&quot;&gt;BeeReaders Review: Features, Pros, Cons, and Alternatives&lt;/a&gt;&lt;/h3&gt;
                    
                                            &lt;div&gt;
                                                        
                                                            &lt;span&gt;
                                                                                                                                                                                    &lt;span&gt;&lt;a href=&quot;https://coruzant.com/software/beereaders-review-features-pros-cons-and-alternatives/#respond&quot;&gt;0&lt;/a&gt;&lt;/span&gt;                                &lt;/span&gt;
                                                    &lt;/div&gt;
                    
                    &lt;div&gt;
If you are researching a BeeReaders review, you are...&lt;/div&gt;
                    
                                    &lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;

        &lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;div&gt;&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&lt;div&gt;&lt;div&gt;

&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;div&gt;
&lt;div&gt;&lt;h3&gt;Politics&lt;/h3&gt;&lt;/div&gt; &lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;

&lt;div&gt;
        &lt;div&gt;
            &lt;div&gt;
                                    &lt;div&gt;
                                                &lt;div&gt;&lt;a href=&quot;https://coruzant.com/mobile/mobile-app-design-what-makes-an-app-easy/&quot;&gt;&lt;/a&gt;&lt;/div&gt;                                                                    &lt;/div&gt;
                
                &lt;div&gt;
                                        
                    &lt;h3&gt;&lt;a href=&quot;https://coruzant.com/mobile/mobile-app-design-what-makes-an-app-easy/&quot;&gt;Mobile App Design: What Makes an App Easy to Use and Worth Keeping&lt;/a&gt;&lt;/h3&gt;
                    
                                            &lt;div&gt;
                                                        
                                                            &lt;span&gt;
                                                                                                                                                                                    &lt;span&gt;&lt;a href=&quot;https://coruzant.com/mobile/mobile-app-design-what-makes-an-app-easy/#respond&quot;&gt;0&lt;/a&gt;&lt;/span&gt;                                &lt;/span&gt;
                                                    &lt;/div&gt;
                    
                    &lt;div&gt;
A new mobile app usually gets one honest shot....&lt;/div&gt;
                    
                                    &lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;

        
        &lt;div&gt;
            &lt;div&gt;
                                    &lt;div&gt;
                                                &lt;div&gt;&lt;a href=&quot;https://coruzant.com/blockchain/web3-development-starts-with-saying-no/&quot;&gt;&lt;/a&gt;&lt;/div&gt;                                                                    &lt;/div&gt;
                
                &lt;div&gt;
                                        
                    &lt;h3&gt;&lt;a href=&quot;https://coruzant.com/blockchain/web3-development-starts-with-saying-no/&quot;&gt;Why Serious Web3 Development Starts With Saying “No”&lt;/a&gt;&lt;/h3&gt;
                    
                                            &lt;div&gt;
                                                        
                                                            &lt;span&gt;
                                                                                                                                                                                    &lt;span&gt;&lt;a href=&quot;https://coruzant.com/blockchain/web3-development-starts-with-saying-no/#respond&quot;&gt;0&lt;/a&gt;&lt;/span&gt;                                &lt;/span&gt;
                                                    &lt;/div&gt;
                    
                    &lt;div&gt;
Most enterprise blockchain pilots end up going nowhere. Teams...&lt;/div&gt;
                    
                                    &lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;

        
        &lt;div&gt;
            &lt;div&gt;
                                    &lt;div&gt;
                                                &lt;div&gt;&lt;a href=&quot;https://coruzant.com/blockchain/crypto-market-making-services-for-tokens/&quot;&gt;&lt;/a&gt;&lt;/div&gt;                                                                    &lt;/div&gt;
                
                &lt;div&gt;
                                        
                    &lt;h3&gt;&lt;a href=&quot;https://coruzant.com/blockchain/crypto-market-making-services-for-tokens/&quot;&gt;Top Crypto Market Making Services for Exchanges and Token Projects&lt;/a&gt;&lt;/h3&gt;
                    
                                            &lt;div&gt;
                                                        
                                                            &lt;span&gt;
                                                                                                                                                                                    &lt;span&gt;&lt;a href=&quot;https://coruzant.com/blockchain/crypto-market-making-services-for-tokens/#respond&quot;&gt;0&lt;/a&gt;&lt;/span&gt;                                &lt;/span&gt;
                                                    &lt;/div&gt;
                    
                    &lt;div&gt;
Liquidity is one of the most important factors in...&lt;/div&gt;
                    
                                    &lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;

        
        &lt;div&gt;
            &lt;div&gt;
                                    &lt;div&gt;
                                                &lt;div&gt;&lt;a href=&quot;https://coruzant.com/digital-strategy/businesses-need-digital-marketing/&quot;&gt;&lt;/a&gt;&lt;/div&gt;                                                                    &lt;/div&gt;
                
                &lt;div&gt;
                                        
                    &lt;h3&gt;&lt;a href=&quot;https://coruzant.com/digital-strategy/businesses-need-digital-marketing/&quot;&gt;Why Growing Businesses Eventually Need a Digital Marketing Agency&lt;/a&gt;&lt;/h3&gt;
                    
                                            &lt;div&gt;
                                                        
                                                            &lt;span&gt;
                                                                                                                                                                                    &lt;span&gt;&lt;a href=&quot;https://coruzant.com/digital-strategy/businesses-need-digital-marketing/#respond&quot;&gt;0&lt;/a&gt;&lt;/span&gt;                                &lt;/span&gt;
                                                    &lt;/div&gt;
                    
                    &lt;div&gt;
Most small businesses start out handling marketing themselves, and...&lt;/div&gt;
                    
                                    &lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;

        
        &lt;div&gt;
            &lt;div&gt;
                                    &lt;div&gt;
                                                &lt;div&gt;&lt;a href=&quot;https://coruzant.com/software/beereaders-review-features-pros-cons-and-alternatives/&quot;&gt;&lt;/a&gt;&lt;/div&gt;                                                                    &lt;/div&gt;
                
                &lt;div&gt;
                                        
                    &lt;h3&gt;&lt;a href=&quot;https://coruzant.com/software/beereaders-review-features-pros-cons-and-alternatives/&quot;&gt;BeeReaders Review: Features, Pros, Cons, and Alternatives&lt;/a&gt;&lt;/h3&gt;
                    
                                            &lt;div&gt;
                                                        
                                                            &lt;span&gt;
                                                                                                                                                                                    &lt;span&gt;&lt;a href=&quot;https://coruzant.com/software/beereaders-review-features-pros-cons-and-alternatives/#respond&quot;&gt;0&lt;/a&gt;&lt;/span&gt;                                &lt;/span&gt;
                                                    &lt;/div&gt;
                    
                    &lt;div&gt;
If you are researching a BeeReaders review, you are...&lt;/div&gt;
                    
                                    &lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;

        &lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&lt;div&gt;&lt;div&gt;

&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;div&gt;
&lt;div&gt;&lt;h3&gt;Strategy&lt;/h3&gt;&lt;/div&gt; &lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;

&lt;div&gt;
        &lt;div&gt;
            &lt;div&gt;
                                    &lt;div&gt;
                                                &lt;div&gt;&lt;a href=&quot;https://coruzant.com/mobile/mobile-app-design-what-makes-an-app-easy/&quot;&gt;&lt;/a&gt;&lt;/div&gt;                                                                    &lt;/div&gt;
                
                &lt;div&gt;
                                        
                    &lt;h3&gt;&lt;a href=&quot;https://coruzant.com/mobile/mobile-app-design-what-makes-an-app-easy/&quot;&gt;Mobile App Design: What Makes an App Easy to Use and Worth Keeping&lt;/a&gt;&lt;/h3&gt;
                    
                                            &lt;div&gt;
                                                        
                                                            &lt;span&gt;
                                                                                                                                                                                    &lt;span&gt;&lt;a href=&quot;https://coruzant.com/mobile/mobile-app-design-what-makes-an-app-easy/#respond&quot;&gt;0&lt;/a&gt;&lt;/span&gt;                                &lt;/span&gt;
                                                    &lt;/div&gt;
                    
                    &lt;div&gt;
A new mobile app usually gets one honest shot....&lt;/div&gt;
                    
                                    &lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;

        
        &lt;div&gt;
            &lt;div&gt;
                                    &lt;div&gt;
                                                &lt;div&gt;&lt;a href=&quot;https://coruzant.com/blockchain/web3-development-starts-with-saying-no/&quot;&gt;&lt;/a&gt;&lt;/div&gt;                                                                    &lt;/div&gt;
                
                &lt;div&gt;
                                        
                    &lt;h3&gt;&lt;a href=&quot;https://coruzant.com/blockchain/web3-development-starts-with-saying-no/&quot;&gt;Why Serious Web3 Development Starts With Saying “No”&lt;/a&gt;&lt;/h3&gt;
                    
                                            &lt;div&gt;
                                                        
                                                            &lt;span&gt;
                                                                                                                                                                                    &lt;span&gt;&lt;a href=&quot;https://coruzant.com/blockchain/web3-development-starts-with-saying-no/#respond&quot;&gt;0&lt;/a&gt;&lt;/span&gt;                                &lt;/span&gt;
                                                    &lt;/div&gt;
                    
                    &lt;div&gt;
Most enterprise blockchain pilots end up going nowhere. Teams...&lt;/div&gt;
                    
                                    &lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;

        
        &lt;div&gt;
            &lt;div&gt;
                                    &lt;div&gt;
                                                &lt;div&gt;&lt;a href=&quot;https://coruzant.com/blockchain/crypto-market-making-services-for-tokens/&quot;&gt;&lt;/a&gt;&lt;/div&gt;                                                                    &lt;/div&gt;
                
                &lt;div&gt;
                                        
                    &lt;h3&gt;&lt;a href=&quot;https://coruzant.com/blockchain/crypto-market-making-services-for-tokens/&quot;&gt;Top Crypto Market Making Services for Exchanges and Token Projects&lt;/a&gt;&lt;/h3&gt;
                    
                                            &lt;div&gt;
                                                        
                                                            &lt;span&gt;
                                                                                                                                                                                    &lt;span&gt;&lt;a href=&quot;https://coruzant.com/blockchain/crypto-market-making-services-for-tokens/#respond&quot;&gt;0&lt;/a&gt;&lt;/span&gt;                                &lt;/span&gt;
                                                    &lt;/div&gt;
                    
                    &lt;div&gt;
Liquidity is one of the most important factors in...&lt;/div&gt;
                    
                                    &lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;

        
        &lt;div&gt;
            &lt;div&gt;
                                    &lt;div&gt;
                                                &lt;div&gt;&lt;a href=&quot;https://coruzant.com/digital-strategy/businesses-need-digital-marketing/&quot;&gt;&lt;/a&gt;&lt;/div&gt;                                                                    &lt;/div&gt;
                
                &lt;div&gt;
                                        
                    &lt;h3&gt;&lt;a href=&quot;https://coruzant.com/digital-strategy/businesses-need-digital-marketing/&quot;&gt;Why Growing Businesses Eventually Need a Digital Marketing Agency&lt;/a&gt;&lt;/h3&gt;
                    
                                            &lt;div&gt;
                                                        
                                                            &lt;span&gt;
                                                                                                                                                                                    &lt;span&gt;&lt;a href=&quot;https://coruzant.com/digital-strategy/businesses-need-digital-marketing/#respond&quot;&gt;0&lt;/a&gt;&lt;/span&gt;                                &lt;/span&gt;
                                                    &lt;/div&gt;
                    
                    &lt;div&gt;
Most small businesses start out handling marketing themselves, and...&lt;/div&gt;
                    
                                    &lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;

        
        &lt;div&gt;
            &lt;div&gt;
                                    &lt;div&gt;
                                                &lt;div&gt;&lt;a href=&quot;https://coruzant.com/software/beereaders-review-features-pros-cons-and-alternatives/&quot;&gt;&lt;/a&gt;&lt;/div&gt;                                                                    &lt;/div&gt;
                
                &lt;div&gt;
                                        
                    &lt;h3&gt;&lt;a href=&quot;https://coruzant.com/software/beereaders-review-features-pros-cons-and-alternatives/&quot;&gt;BeeReaders Review: Features, Pros, Cons, and Alternatives&lt;/a&gt;&lt;/h3&gt;
                    
                                            &lt;div&gt;
                                                        
                                                            &lt;span&gt;
                                                                                                                                                                                    &lt;span&gt;&lt;a href=&quot;https://coruzant.com/software/beereaders-review-features-pros-cons-and-alternatives/#respond&quot;&gt;0&lt;/a&gt;&lt;/span&gt;                                &lt;/span&gt;
                                                    &lt;/div&gt;
                    
                    &lt;div&gt;
If you are researching a BeeReaders review, you are...&lt;/div&gt;
                    
                                    &lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;

        &lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;</content:encoded>
</item>
<item>
<title>15 Features Of The Best Plumbing Websites With Examples</title>
<link>https://valveandmeter.com/blog/web/best-plumbing-websites/</link>
<guid isPermaLink="false">UwK9AThB5BzuCKvkZDjImHyVS-qxNeIznUf7tg==</guid>
<pubDate>Thu, 16 Jul 2026 06:29:59 +0000</pubDate>
<description>What Defines The Best Plumbing Websites? Your plumbing company website represents your business to residential and commercial clients alike. The most effective web design can set your plumbing apart from others. Ask yourself these questions: How can my website help me stand out from other plumbers near me? How can I get my website to […] The post 15 Features Of The Best Plumbing Websites With Examples appeared first on Valve + Meter.</description>
<content:encoded>&lt;h2&gt;What Defines The Best Plumbing Websites?&lt;/h2&gt;&lt;p&gt;Your plumbing company website represents your business to residential and commercial clients alike. The most effective web design can set your plumbing apart from others.&lt;/p&gt;&lt;p&gt;Ask yourself these questions:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;How can my website help me stand out from other plumbers near me?&lt;/strong&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;How can I get my website to show up when people search for plumbers?&lt;/strong&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Is my website built to actually bring in more leads and customers for my plumbing business?&lt;/strong&gt;&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;In this article, Valve+Meter Performance Marketing explores 15 key features every plumbing company’s website needs. Our &lt;a href=&quot;https://valveandmeter.com/trades/plumbing/web/&quot;&gt;plumbing website design and development team&lt;/a&gt; explains why each feature can make an impact in answering each of these questions. Packed with examples from some of the best plumbing websites from across the country, you can find inspiration and a plan to make your plumbing website stand out and maximize performance.&lt;/p&gt;&lt;h3&gt;15 Key Features of the Best Plumbing Websites:&lt;/h3&gt;&lt;p&gt;A successful plumbing website combines aesthetics with functionality, offering a seamless user experience while effectively showcasing the company’s services and expertise. It’s a platform that not only attracts potential customers but also converts them into loyal clients.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Professional Design&lt;/li&gt;&lt;li&gt;Mobile-Friendly&lt;/li&gt;&lt;li&gt;Fast Loading Speed&lt;/li&gt;&lt;li&gt;Clear Contact Information&lt;/li&gt;&lt;li&gt;Online Booking or Quote Requests&lt;/li&gt;&lt;li&gt;Services Offered&lt;/li&gt;&lt;li&gt;About Us Page&lt;/li&gt;&lt;li&gt;Social Proof&lt;/li&gt;&lt;li&gt;FAQ Section&lt;/li&gt;&lt;li&gt;Blog or Resources Section&lt;/li&gt;&lt;li&gt;Clear Call-to-Action (CTA)&lt;/li&gt;&lt;li&gt;SEO Optimization&lt;/li&gt;&lt;li&gt;Social Media Integration&lt;/li&gt;&lt;li&gt;Localized Content&lt;/li&gt;&lt;li&gt;Easy Navigation&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;Professional Plumbing Website Design: Reflecting Brand Identity&lt;/h2&gt;&lt;p&gt;The significance of a clean, modern design in a plumbing website cannot be overstated. It’s the first thing visitors notice, and it sets the tone for their entire experience.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;How does your website compare to all the plumbing companies in your service area?&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;A professional design reflects the brand’s identity, showcasing your values and brand personality. Your plumbing website content has the ability to make a connection with your target audience that is instantly memorable.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://ifvp.org/content/why-our-brain-loves-pictures#:~:text=Images%20are%20superior%20to%20text,to%20the%20brain%20is%20visual.&quot;&gt;&lt;strong&gt;90%&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt;of the information transmitted to our brains is visual.&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Website design is not just about looking good; it’s about creating an environment where visitors feel comfortable and confident in the plumbing services offered.&lt;/p&gt;&lt;p&gt;A well-designed site conveys reliability and expertise, essential qualities for any plumbing business. It tells potential customers that the company pays attention to detail and is committed to providing a positive experience, both online and offline.&lt;/p&gt;&lt;p&gt;In a competitive market, a standout design can be the deciding factor that turns a website visitor into a loyal customer.&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://valveandmeter.com/wp-content/uploads/2024/03/ravinia-plumbing-scaled.webp&quot; alt=&quot;Plumbing Website Design Example: Ravinia Plumbing&quot; title=&quot;&quot;/&gt;&lt;/p&gt;&lt;h3&gt;Professional Website Design Example: Ravinia Plumbing&lt;/h3&gt;&lt;p&gt;&lt;a href=&quot;https://raviniaplumbing.com/&quot;&gt;Ravinia Plumbing, Sewer, Heating, &amp;amp; Electrical Services&lt;/a&gt; has a clean and modern design that showcases their brand logo clearly. Plumbing businesses often offer a range of services including residential plumbing and commercial plumbing services.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;An expert plumbing website designer understands how to keep visitors’ attention on next steps with prominent calls to action.&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Other key design features to build brand identity include:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Consistent use of brand colors, logo, and typography.&lt;/li&gt;&lt;li&gt;Detailed “About Us” page highlighting company story, values, and mission.&lt;/li&gt;&lt;li&gt;High-quality, professional imagery and videos.&lt;/li&gt;&lt;li&gt;Clear, concise messaging that communicates unique selling points.&lt;/li&gt;&lt;li&gt;Customer testimonials and reviews to build trust and credibility.&lt;/li&gt;&lt;li&gt;Easy-to-navigate menu that reflects brand’s priorities.&lt;/li&gt;&lt;li&gt;Blog or resource section showcasing expertise and industry knowledge.&lt;/li&gt;&lt;li&gt;Contact information and social media links for easy communication.&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;Mobile-Friendly: Catering to All Devices&lt;/h2&gt;&lt;p&gt;When a plumbing emergency happens day or night, prospective customers grab the first thing at hand. It is literally a smartphone for residential and commercial customers.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;The importance of a responsive plumbing website design cannot be overstated.&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;The best plumbing website design creates an optimal user experience for smartphones, tablets, and desktop devices.&lt;/p&gt;&lt;p&gt;A mobile-friendly website ensures you stay competitive against other plumbing companies. When visitors can find all your plumbing services, they’re more likely to engage with your website and convert into plumbing leads.&lt;/p&gt;&lt;p&gt;Furthermore, mobile-friendly plumbing websites impact SEO significantly. Websites optimized for mobile devices rank higher in search engine results, making them more visible to potential customers.&lt;/p&gt;&lt;p&gt;Plumber websites must offer a smooth mobile experience that keeps users engaged, reducing bounce rates and increasing the likelihood of converting visitors into customers.&lt;/p&gt;&lt;p&gt;###&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://valveandmeter.com/wp-content/uploads/2026/06/baywater-plumbing-1.webp&quot; alt=&quot;&quot; title=&quot;&quot;/&gt;&lt;/p&gt;&lt;h3&gt;Mobile-Friendly Plumbing Website: Bay Water Plumbing&lt;/h3&gt;&lt;p&gt;&lt;a href=&quot;https://baywaterplumbing.com/&quot;&gt;Bay Water Plumbing&lt;/a&gt; incorporates a seamless experience across all devices. This plumbing website responds to the user’s device with a prominent phone number at the top of the page for website visitors ready to call now.&lt;/p&gt;&lt;p&gt;Additional features of responsive plumber websites include:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Fast loading times to reduce bounce rates.&lt;/li&gt;&lt;li&gt;Easy-to-use navigation for touchscreens.&lt;/li&gt;&lt;li&gt;Click-to-call buttons for immediate contact.&lt;/li&gt;&lt;li&gt;Simplified forms for easy submission on mobile devices.&lt;/li&gt;&lt;li&gt;Readable font sizes and clear text spacing.&lt;/li&gt;&lt;li&gt;Compressed images for quicker loading on mobile networks.&lt;/li&gt;&lt;li&gt;Accessible menus and dropdowns for seamless navigation.&lt;/li&gt;&lt;li&gt;Focused on content for quick access to essential information.&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;Fast Loading Speed: Keeping Visitors Engaged&lt;/h2&gt;&lt;p&gt;When people need emergency services, they won’t wait for a slow response.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Slow websites are likely the hold music of the digital age.&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Slow-loading websites test the patience of users, who’re likely to leave if a page takes too long to load. This not only affects user satisfaction but also harms the website’s search engine ranking, as speed is a key factor in SEO algorithms.&lt;/p&gt;&lt;p&gt;Web developers who understand performance first build the best plumbing websites. Your plumbing company can look great online without sacrificing website speed.&lt;/p&gt;&lt;p&gt;Compressing images, leveraging browser caching, and minimizing the use of heavy scripts and plugins can help your plumbing company website delight users and earn more plumbing leads.&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://valveandmeter.com/wp-content/uploads/2024/03/Superior-Plumbing-scaled.webp&quot; alt=&quot;Plumbing Website Design Example: Superior Plumbing&quot; title=&quot;&quot;/&gt;&lt;/p&gt;&lt;h3&gt;Fast Loading Speed Example: Superior Plumbing&lt;/h3&gt;&lt;p&gt;&lt;a href=&quot;https://www.superiorplumbing.com/&quot;&gt;Superior Plumbing&lt;/a&gt; earns an incredible 96/100 according to the &lt;a href=&quot;https://lighthouse-metrics.com/&quot;&gt;Lighthouse Metrics&lt;/a&gt;. This plumber website eliminates features that slow the website without sacrificing quality design. The phone number is prominent, text matches branding, and the &lt;a href=&quot;https://valveandmeter.com/blog/plumbing/plumber-slogans/&quot;&gt;plumbing slogan&lt;/a&gt; is on clear display: “Plumbing Repairs + Honest Answers.”&lt;/p&gt;&lt;p&gt;Additional ways to improve the speed and performance of a plumbing website:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Compress and optimize images to reduce file sizes.&lt;/li&gt;&lt;li&gt;Minimize heavy graphics and animation use.&lt;/li&gt;&lt;li&gt;Enable browser caching to reduce load times for repeat visitors.&lt;/li&gt;&lt;li&gt;Minimize the use of plugins and external scripts.&lt;/li&gt;&lt;li&gt;Optimize CSS and JavaScript files by minifying and combining them.&lt;/li&gt;&lt;li&gt;Choose a reliable and fast web hosting service.&lt;/li&gt;&lt;li&gt;Set up lazy loading for images and videos.&lt;/li&gt;&lt;li&gt;Reduce the number of HTTP requests by consolidating resources.&lt;/li&gt;&lt;li&gt;Regularly monitor and test website speed.&lt;/li&gt;&lt;li&gt;Keep your website’s software and plugins up to date for optimal performance.&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;Clear Contact Information: Ensuring Accessibility&lt;/h2&gt;&lt;p&gt;So your plumber website looks great and performs exceptionally across all devices. When a potential customer arrives, the best plumber websites are intuitive.&lt;/p&gt;&lt;p&gt;Modern plumbing websites can be packed with many service pages, videos, blogs, and even podcasts.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Don’t let marketing stand in the way of converting happy customers.&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Performance-focused plumbing website design puts a clear call to action ahead of all other features. Your website’s job is to convert visitors to connect with your sales and support staff.&lt;/p&gt;&lt;p&gt;Visitors should be able to find phone numbers, email addresses, and physical locations without having to search extensively. This accessibility is crucial for customer convenience and can significantly impact the decision to choose your services over a competitor’s.&lt;/p&gt;&lt;p&gt;Best practices for displaying contact information include placing it prominently on the homepage, in the website’s header, and in a dedicated ‘Contact Us’ section.&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://valveandmeter.com/wp-content/uploads/2024/03/penguin-scaled.webp&quot; alt=&quot;Plumbing Website Design Example: Penguin Plumbing&quot; title=&quot;&quot;/&gt;&lt;/p&gt;&lt;h3&gt;Example Of A Great Website CTA: Penguin Air, Plumbing, Electrical&lt;/h3&gt;&lt;p&gt;&lt;a href=&quot;https://www.penguinair.com/&quot;&gt;Penguin Air, Plumbing, Electrical&lt;/a&gt; offers one of the best plumber websites in terms of easy contact details. On the homepage of the business website, the phone and contact form leap off the page in branded colors.&lt;/p&gt;&lt;p&gt;Across every webpage the phone number and service schedule options are just a click away.&lt;/p&gt;&lt;p&gt;Contact details improve user experience and can help with search engine visibility. Here are the contact details that should be prominently displayed on a plumber website:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Phone number&lt;/li&gt;&lt;li&gt;Emergency service hotline (if offered)&lt;/li&gt;&lt;li&gt;Contact form on homepage and link in header&lt;/li&gt;&lt;li&gt;Business name, address, phone (NAP) in footer&lt;/li&gt;&lt;li&gt;Social media profiles (Facebook, Instagram, LinkedIn, etc.)&lt;/li&gt;&lt;li&gt;Business hours&lt;/li&gt;&lt;li&gt;Map or directions to the physical location (if applicable)&lt;/li&gt;&lt;li&gt;Links to review sites (e.g., Yelp, Google Reviews)&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;Online Booking or Quote Requests: Simplifying Customer Interaction&lt;/h2&gt;&lt;p&gt;Incorporating online booking or quote requests into a plumbing website offers numerous benefits. It provides a convenient way for potential customers to schedule appointments or request pricing information without the need for phone calls or emails.&lt;/p&gt;&lt;p&gt;This streamlined process can lead to increased conversions and customer satisfaction and all the best plumbing websites use forms or plugins to provide plumbing leads a quick solution for plumbing services.&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://valveandmeter.com/wp-content/uploads/2024/03/hoffman-bros-scaled.webp&quot; alt=&quot;Plumbing Website Design Example: Hoffman Brothers&quot; title=&quot;&quot;/&gt;&lt;/p&gt;&lt;h3&gt;Example Of Online Booking: Hoffman Brothers&lt;/h3&gt;&lt;p&gt;With an expansive range of services and locations in St. Louis and Nashville, &lt;a href=&quot;https://www.hoffmannbros.com/plumbing&quot;&gt;Hoffman Brothers&lt;/a&gt;‘ website could become crowded and difficult to use.&lt;/p&gt;&lt;p&gt;Instead, they wisely offer potential customers a simple option to book online or request an estimate across the primary service pages.&lt;/p&gt;&lt;p&gt;The best plumbing websites feature:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Buttons for “Schedule an Appointment” or “Request a Quote” on the homepage and service pages.&lt;/li&gt;&lt;li&gt;User-friendly booking forms with simplified fields.&lt;/li&gt;&lt;li&gt;Real-time availability calendars to allow customers to select their preferred appointment slots.&lt;/li&gt;&lt;li&gt;Automated email or SMS confirmations and reminders to keep customers informed.&lt;/li&gt;&lt;li&gt;Integration with payment gateways for secure online payments or deposits, if applicable.&lt;/li&gt;&lt;li&gt;Option for customers to describe their plumbing issue in detail for a more accurate quote.&lt;/li&gt;&lt;li&gt;Estimated response time displayed on the booking page to set clear expectations for customers.&lt;/li&gt;&lt;li&gt;Mobile optimization to ensure the booking process is seamless on smartphones and tablets.&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;Services Offered: Informing Potential Customers&lt;/h2&gt;&lt;p&gt;A detailed services page is vital for informing potential customers about what your plumbing business offers.&lt;/p&gt;&lt;p&gt;It should clearly outline the range of services provided, from routine maintenance to emergency repairs, and everything in between. This transparency helps visitors understand whether your company can meet their specific needs.&lt;/p&gt;&lt;p&gt;The best plumbing websites also design service pages for improved site navigation and SEO.&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://valveandmeter.com/wp-content/uploads/2024/03/Oneill-plumbing-company-scaled.webp&quot; alt=&quot;Plumbing Website Design Example: O&amp;#39;Neill Plumbing Company&quot; title=&quot;&quot;/&gt;&lt;/p&gt;&lt;h3&gt;Services Page Example: O’Neill Plumbing Company&lt;/h3&gt;&lt;p&gt;&lt;a href=&quot;https://oneillplumbing.com/services/&quot;&gt;O’Neill Plumbing Company&lt;/a&gt; incorporates a services page that’s well-organized and easy to read. Visitors can choose between general plumbing services or navigate to underground utilities and construction services sub-pages.&lt;/p&gt;&lt;p&gt;Services pages can simplify websites with many services when thoughtfully designed, including:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Clear headings and descriptions for easy information access.&lt;/li&gt;&lt;li&gt;High-quality images and videos enhance service presentation.&lt;/li&gt;&lt;li&gt;Testimonials and case studies build trust and credibility.&lt;/li&gt;&lt;li&gt;Internal linking increases specific service pages’ visibility.&lt;/li&gt;&lt;li&gt;Professional design and SEO guide user and search navigation.&lt;/li&gt;&lt;li&gt;Organized services page improves site navigation efficiency.&lt;/li&gt;&lt;li&gt;Calls to action on the services page encourage engagement.&lt;/li&gt;&lt;li&gt;service information retains user attention, boosts rankings.&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;About Us Page: Building Trust and Connection&lt;/h2&gt;&lt;p&gt;The About Us page plays a crucial role in establishing credibility for a plumbing business. It’s where you share your company’s story, values, and the team behind the services. This personal touch helps build trust and connect with potential customers on a deeper level.&lt;/p&gt;&lt;p&gt;Elements of a compelling About Us section include a brief history of the company, the qualifications and experience of the team, and the company’s mission and values. Including team photos and any awards or recognitions can further enhance credibility and relatability.&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://valveandmeter.com/wp-content/uploads/2024/03/applewood-scaled.webp&quot; alt=&quot;Plumbing Website Design Example: Applewood&quot; title=&quot;&quot;/&gt;&lt;/p&gt;&lt;h3&gt;Example Of Great Plumbing Website About Us: Applewood FixIt&lt;/h3&gt;&lt;p&gt;&lt;a href=&quot;https://www.applewoodfixit.com/about-us/&quot;&gt;Applewood Plumbing, Heating &amp;amp; Electric&lt;/a&gt; checks all the boxes with their About Us page. With a clear value proposition at the top of the page, this may be one of the best plumbing industry websites.&lt;/p&gt;&lt;p&gt;There’s a clear story to be told on your about us page and Applewood FixIt nails everyone including:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Value proposition that states the ‘Why’ of your business.&lt;/li&gt;&lt;li&gt;Customer-focused content across the page.&lt;/li&gt;&lt;li&gt;Brand showcase including images of your team, vehicles, and showroom.&lt;/li&gt;&lt;li&gt;Industry awards and community recognition.&lt;/li&gt;&lt;li&gt;Code of ethics and company values.&lt;/li&gt;&lt;li&gt;Membership programs, warranties, referral offers, and other branded offers.&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;Social Proof: Enhancing Credibility&lt;/h2&gt;&lt;p&gt;Positive feedback from past customers can significantly enhance the trustworthiness of your plumbing business.&lt;/p&gt;&lt;p&gt;Testimonials and reviews serve as social proof, showing potential customers that others have had satisfactory experiences with your services.&lt;/p&gt;&lt;p&gt;Best practices for showcasing testimonials and reviews include placing them prominently on the website, ensuring they’re genuine and up-to-date, and including the reviewer’s name and location for added authenticity.&lt;/p&gt;&lt;p&gt;You can also link to third-party review sites like Yelp or Google My Business for more feedback.&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://valveandmeter.com/wp-content/uploads/2024/03/pink-plumber-scaled.webp&quot; alt=&quot;Plumbing Website Design Example: Pink Plumber&quot; title=&quot;&quot;/&gt;&lt;/p&gt;&lt;h3&gt;Social Proof Example: Pink Plumber&lt;/h3&gt;&lt;p&gt;&lt;a href=&quot;https://thepinkplumber.com/&quot;&gt;Pink Plumber&lt;/a&gt; is an example of innovative design and a great example of simplified social proof. From their homepage, potential customers can find why this Atlanta area plumbing company can be trusted.&lt;/p&gt;&lt;p&gt;It’s worth noting that creating a dedicated page within links to testimonials, directories, and reviews is highly recommended to improve user experience and gain even more reviews from your happy customers.&lt;/p&gt;&lt;h2&gt;FAQ Section: Addressing Common Concerns&lt;/h2&gt;&lt;p&gt;A well-structured FAQ section can provide immense benefits to both your plumbing business and website visitors.&lt;/p&gt;&lt;p&gt;It addresses common questions and concerns, saving time for both parties and providing valuable information upfront.&lt;/p&gt;&lt;p&gt;Tips for creating informative and helpful FAQs include grouping questions into relevant categories, using clear and concise language, and regularly updating the section based on new queries and feedback.&lt;/p&gt;&lt;p&gt;Providing answers that are informative and to the point can significantly enhance the user experience and establish your business as a helpful and knowledgeable authority in the plumbing industry.&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://valveandmeter.com/wp-content/uploads/2024/03/Summers-and-zims-scaled.webp&quot; alt=&quot;Plumbing Website Design Example: Summers and Zim&amp;#39;s&quot; title=&quot;&quot;/&gt;&lt;/p&gt;&lt;h3&gt;Example Of Plumber FAQ: Summers and Zim’s&lt;/h3&gt;&lt;p&gt;&lt;a href=&quot;https://sumzim.com/services/plumbing/plumbing-faqs/&quot;&gt;Summers and Zim’s&lt;/a&gt; is one of the best plumbing websites they show expertise and customer-focused attention.&lt;/p&gt;&lt;p&gt;An FAQ doesn’t need to be complex and can build over time. It’s also a great way to begin building a strong blog and resources sections as we’ll explore next.&lt;/p&gt;&lt;h2&gt;Blog or Resources Section: Establishing Authority&lt;/h2&gt;&lt;p&gt;Content marketing, through a blog or resources section, plays a pivotal role in both &lt;a href=&quot;https://valveandmeter.com/trades/plumbing/local-seo/&quot;&gt;SEO and authority building for a plumbing website&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;By providing valuable and informative content, you can attract more visitors to your site and position your business as an expert in the field.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://valveandmeter.com/blog/plumbing/24-must-read-plumbing-blog-topics/&quot;&gt;Ideas for valuable blog topics&lt;/a&gt; don’t only live on your website. You can use blog posts to inspire social media posts, email marketing campaigns, and even script videos for your plumbing company.&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://valveandmeter.com/wp-content/uploads/2024/03/dave-jones-scaled.webp&quot; alt=&quot;Plumbing Website Design Example: Dave Jones&quot; title=&quot;&quot;/&gt;&lt;/p&gt;&lt;h3&gt;Plumbing Blog Example: Dave Jones&lt;/h3&gt;&lt;p&gt;&lt;a href=&quot;https://www.davejonesllc.com/blog/&quot;&gt;Dave Jones&lt;/a&gt; uses a clean, modern plumbing design across their website. This extends to their blog that uses original images and a carousel to keep readers engaged with featured blogs.&lt;/p&gt;&lt;p&gt;Tips for a successful plumbing blog include:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Identify relevant and trending topics in the plumbing industry.&lt;/li&gt;&lt;li&gt;Write informative and engaging content that provides value to your readers.&lt;/li&gt;&lt;li&gt;Use keywords strategically to improve SEO and visibility.&lt;/li&gt;&lt;li&gt;Include high-quality images and videos to enhance your posts.&lt;/li&gt;&lt;li&gt;Update your blog regularly to keep content fresh and attract repeat visitors.&lt;/li&gt;&lt;li&gt;Promote your blog posts on social media and other channels to increase reach.&lt;/li&gt;&lt;li&gt;Engage your readers by responding to comments and encouraging discussion.&lt;/li&gt;&lt;li&gt;Collaborate with other industry professionals or guest bloggers to expand your audience.&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;Clear Call-to-Action (CTA): Guiding Visitor Actions&lt;/h2&gt;&lt;p&gt;As we discussed early on, online booking, phone numbers, chatbots, and contact forms are key to the best plumbing websites converting leads.&lt;/p&gt;&lt;p&gt;Clear CTAs are crucial for guiding visitor actions on a plumbing website. This means they should stand out visually and convey a clear message on every page of your website.&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://valveandmeter.com/wp-content/uploads/2024/03/lee-company-scaled.webp&quot; alt=&quot;Plumbing Website Design Example: Lee Company&quot; title=&quot;&quot;/&gt;&lt;/p&gt;&lt;h3&gt;CTA Example For Plumbing Website: Lee Company&lt;/h3&gt;&lt;p&gt;&lt;a href=&quot;https://www.leecompany.com/&quot;&gt;Lee Company&lt;/a&gt; offers a vast range of services including home services and plumbing. For enterprise websites, a clear call to action can help website visitors cut through the confusion of complex navigation and connect with your sales team.&lt;/p&gt;&lt;p&gt;The blue Schedule Online button stands out and lets visitors request plumbing services quickly.&lt;/p&gt;&lt;p&gt;Here are some best practices for call-to-action (CTA) on a plumbing website:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Use clear and concise language that communicates the action you want users to take (e.g., “Schedule an Appointment,” “Get a Free Quote”).&lt;/li&gt;&lt;li&gt;Make CTAs visually stand out with contrasting colors or buttons.&lt;/li&gt;&lt;li&gt;Place CTAs strategically throughout the website, especially above the fold and near relevant content.&lt;/li&gt;&lt;li&gt;Personalize CTAs based on the user’s journey or the page content.&lt;/li&gt;&lt;li&gt;Keep forms simple and easy to fill to encourage conversions.&lt;/li&gt;&lt;li&gt;Test different CTA placements, wording, and designs to optimize performance.&lt;/li&gt;&lt;li&gt;Use action-oriented verbs to create a sense of urgency (e.g., “Call Now,” “Book Today”).&lt;/li&gt;&lt;li&gt;Provide multiple contact options (phone, email, contact form) to cater to different user preferences.&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;SEO Optimization: Improving Visibility&lt;/h2&gt;&lt;p&gt;SEO optimization is essential for improving the visibility of a plumbing website in search engine results. This includes extensive content management, competitor analysis, and keyword research.&lt;/p&gt;&lt;p&gt;Beyond the visible sections of a website, the reason one plumbing company always seems to appear at the top of search results include technical SEO and off-page SEO strategies.&lt;/p&gt;&lt;p&gt;Incorporating SEO into a website is part of a &lt;a href=&quot;https://valveandmeter.com/blog/plumbing/what-are-the-6-best-plumbing-marketing-strategies/&quot;&gt;digital marketing strategy for plumbers&lt;/a&gt; to succeed in the modern era. SEO helps search engines understand what your website is about and improves the chances of your site appearing higher in search results.&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://valveandmeter.com/wp-content/uploads/2024/03/thomas-and-galbraith-scaled.webp&quot; alt=&quot;Plumbing Website Design Example: Thomas and Galbraith&quot; title=&quot;&quot;/&gt;&lt;/p&gt;&lt;h3&gt;Example Of Plumbing SEO: Thomas &amp;amp; Galbraith&lt;/h3&gt;&lt;p&gt;When someone searches for plumbing services in Cincinnati and surrounding communities, &lt;a href=&quot;https://thomasgalbraith.com/plumbing/&quot;&gt;Thomas &amp;amp; Galbraith&lt;/a&gt; consistently rank in the top of organic search results.&lt;/p&gt;&lt;p&gt;Practices that the best plumbing websites use to improve search visibility include:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Optimized title tags and meta descriptions with relevant keywords.&lt;/li&gt;&lt;li&gt;High-quality, keyword-rich content that addresses user queries.&lt;/li&gt;&lt;li&gt;Mobile-friendly design for better ranking on mobile searches.&lt;/li&gt;&lt;li&gt;Fast loading speed to improve user experience and SEO.&lt;/li&gt;&lt;li&gt;Local SEO optimization with NAP (Name, Address, Phone) details.&lt;/li&gt;&lt;li&gt;Structured data markup for &lt;a href=&quot;https://valveandmeter.com/local-seo/glossary/&quot;&gt;rich snippets&lt;/a&gt; in search results.&lt;/li&gt;&lt;li&gt;Secure website with HTTPS protocol for better ranking and security.&lt;/li&gt;&lt;li&gt;User-friendly navigation and &lt;a href=&quot;https://valveandmeter.com/seo/glossary/&quot;&gt;URL structure&lt;/a&gt; for better indexing.&lt;/li&gt;&lt;li&gt;High-quality backlinks from reputable sources to build authority.&lt;/li&gt;&lt;li&gt;Regularly updated blog or resources section for fresh content.&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;Social Media Integration: Expanding Reach&lt;/h2&gt;&lt;p&gt;Linking to social media profiles from a plumbing website offers numerous benefits.&lt;/p&gt;&lt;p&gt;It provides an additional platform for engaging with potential customers, showcasing your work, and sharing valuable content. Social media integration can also increase your brand’s visibility and drive more traffic to your website.&lt;/p&gt;&lt;p&gt;If your plumbing business is active on social media, your website design can connect happy customers to your accounts.&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://valveandmeter.com/wp-content/uploads/2024/03/Fast-plumbing-scaled.webp&quot; alt=&quot;Plumbing Website Design Example: Fast Plumbing&quot; title=&quot;&quot;/&gt;&lt;/p&gt;&lt;h3&gt;Social Media Integration: Fast Plumbing&lt;/h3&gt;&lt;p&gt;&lt;a href=&quot;https://fastplumbingaustin.com/&quot;&gt;Fast Plumbing&lt;/a&gt; in Austin, Texas features social media links prominently in the upper left of their homepage. This business website offers customers a range of social media including Facebook, Twitter, and LinkedIn.&lt;/p&gt;&lt;p&gt;They also link to YouTube and Google Business Profile to showcase videos and images of recent projects.&lt;/p&gt;&lt;p&gt;Consider how these social channels can improve your overall plumbing marketing strategy:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Increases brand visibility and online presence.&lt;/li&gt;&lt;li&gt;Builds trust by showcasing active engagement with customers.&lt;/li&gt;&lt;li&gt;Drives traffic from social platforms to your website.&lt;/li&gt;&lt;li&gt;Enhances SEO through social signals and backlinks.&lt;/li&gt;&lt;li&gt;Provides an additional channel for customer support and interaction.&lt;/li&gt;&lt;li&gt;Showcases your work and expertise through shared content.&lt;/li&gt;&lt;li&gt;Encourages word-of-mouth marketing as users share your content.&lt;/li&gt;&lt;li&gt;Allows targeted advertising and promotions on social platforms.&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;Localized Content: Connecting with Your Community&lt;/h2&gt;&lt;p&gt;Localized content establishes you offer services and connect with your community.&lt;/p&gt;&lt;p&gt;Google and other search engines display local results when someone searches “plumber near me” or “plumbing services in Warsaw.”&lt;/p&gt;&lt;p&gt;By tailoring your content to specific geographic locations, you can make your website more relevant and appealing to potential customers in your service area.&lt;/p&gt;&lt;p&gt;The best plumbing websites make a connection with your target audience and also raise your prominence with search crawlers about your service area.&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://valveandmeter.com/wp-content/uploads/2024/03/flowtech-scaled.webp&quot; alt=&quot;Plumbing Website Design Example: Flowtech&quot; title=&quot;&quot;/&gt;&lt;/p&gt;&lt;h3&gt;Local Plumber Services Example: &lt;a href=&quot;https://flowtechpandh.com/&quot;&gt;Flow-Tech Plumbing and Heating&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Flow-Tech Plumbing and Heating features 5 locations on their homepage. With plumbing services offered across a broad region, their plumbing website design includes a service areas section.&lt;/p&gt;&lt;p&gt;This helps both residential plumbing and commercial businesses searching for service discover Flow-Tech. Although their &lt;a href=&quot;https://valveandmeter.com/blog/plumbing/essential-guide-to-local-seo-for-plumbers/&quot;&gt;plumbing website content strengthens local SEO&lt;/a&gt;, the design stays focused on the website visitors.&lt;/p&gt;&lt;p&gt;Localized website design considerations include:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Dedicated pages for each service area or city you serve.&lt;/li&gt;&lt;li&gt;Local testimonials or case studies showcasing your work in specific neighborhoods or towns.&lt;/li&gt;&lt;li&gt;Tips for dealing with common plumbing issues specific to your area (e.g., hard water, seasonal changes).&lt;/li&gt;&lt;li&gt;Blog posts or articles about local community events or initiatives you’re involved in.&lt;/li&gt;&lt;li&gt;Local landmarks or references in your website’s imagery or descriptions.&lt;/li&gt;&lt;li&gt;A map showing your service areas or locations.&lt;/li&gt;&lt;li&gt;Local awards, certifications, or recognition your business has received.&lt;/li&gt;&lt;li&gt;Emergency plumbing services available in specific areas.&lt;/li&gt;&lt;li&gt;Seasonal plumbing maintenance tips relevant to your region.&lt;/li&gt;&lt;li&gt;Collaboration or partnerships with other local businesses or organizations.&lt;/li&gt;&lt;li&gt;Mention of local schools, parks, or landmarks near your business location.&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;Easy Navigation: Enhancing User Experience&lt;/h2&gt;&lt;p&gt;Intuitive layout and organization play a crucial role in user satisfaction on a plumbing website. Visitors should be able to find the information they need quickly and easily, without getting frustrated or lost.&lt;/p&gt;&lt;p&gt;Best practices for website navigation include having a clear and concise menu, using descriptive labels for menu items, and organizing content logically.&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://valveandmeter.com/wp-content/uploads/2024/03/ingram-plumbing-scaled.webp&quot; alt=&quot;Plumbing Website Design Example: Ingram Plumbing&quot; title=&quot;&quot;/&gt;&lt;/p&gt;&lt;h3&gt;Easy Navigation Example: Ingram Plumbing&lt;/h3&gt;&lt;p&gt;&lt;a href=&quot;https://www.ingramplumbing.com/&quot;&gt;Ingram Plumbing&lt;/a&gt; makes it easy for users to find the specific information and plumbing services with a clear navigation menu.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Clear and concise menu labels.&lt;/li&gt;&lt;li&gt;Sticky navigation menu for easy access.&lt;/li&gt;&lt;li&gt;Search bar for quick content finding.&lt;/li&gt;&lt;li&gt;Breadcrumb navigation for site hierarchy.&lt;/li&gt;&lt;li&gt;Dropdown menus for subcategories.&lt;/li&gt;&lt;li&gt;Footer links to important pages.&lt;/li&gt;&lt;li&gt;Logical and intuitive page layout.&lt;/li&gt;&lt;li&gt;Visible contact information.&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;How To Build The Best Plumbing Website&lt;/h2&gt;&lt;p&gt;&lt;a href=&quot;https://valveandmeter.com/trades/plumbing/web/&quot;&gt;Creating a winning plumbing website&lt;/a&gt; involves more than just listing your services. It’s about crafting an online presence that reflects your brand identity, caters to your audience’s needs, and stands out in a competitive market.&lt;/p&gt;&lt;p&gt;At Valve+Meter, our team collaborates with businesses to design plumbing websites that represent your unique services.&lt;/p&gt;&lt;p&gt;By incorporating features such as a professional design, mobile-friendly layout, fast loading speed, clear contact information, and online booking options, you can enhance user experience and build trust with potential customers.&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://valveandmeter.com/wp-content/uploads/2024/03/best-plumbing-websites-2.webp&quot; alt=&quot;Top plumbing websites share several key features. When assessing your current plumbing company website or consulting with professional designers, consider the following: Professional design that stands out from other plumbing companies Mobile-friendly layout that showcases all of your plumbing services Fast loading speeds across all devices Clear contact information Online booking or quote request options List of services offered in the navigation menu and footer About Us page Testimonials and reviews FAQ section Blog or resources section with localized content Local SEO optimization, including city pages and local service area content Social media integration Security features Easy navigation&quot; title=&quot;&quot;/&gt;&lt;/p&gt;&lt;p&gt;Additionally, showcasing your services, sharing customer testimonials, providing valuable resources, and optimizing for SEO will help establish your authority in the industry and improve your website’s visibility.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Great plumbing website designs start with your customers and the services you provide to your community.&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;From there, you need a team of experienced professionals in design, web development, content marketing, and search engine optimization to ensure your plumbing website is one of the best in the world.&lt;/p&gt;&lt;p&gt;Your website is an extension of your plumbing business. Just like your vehicles, support staff, and plumbers, it should represent the best your business has to offer.&lt;/p&gt;&lt;h2&gt;Connect With Plumbing Design Experts&lt;/h2&gt;&lt;p&gt;Interested in a new plumbing website or a redesign? Valve+Meter provides consultations and a free marketing assessment to understand how our team can make collaborate with your plumbing business.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://valveandmeter.com/request-marketing-assessment/&quot;&gt;Request Marketing Analysis&lt;/a&gt;&lt;/p&gt;&lt;h2&gt;Frequently Asked Questions&lt;/h2&gt;&lt;h3&gt;What are the key features of the best plumbing websites?&lt;/h3&gt;&lt;p&gt;Top plumbing websites share several key features. When assessing your current plumbing company website or consulting with professional designers, consider the following:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Professional design that stands out from other plumbing companies&lt;/li&gt;&lt;li&gt;Mobile-friendly layout that showcases all of your plumbing services&lt;/li&gt;&lt;li&gt;Fast loading speeds across all devices&lt;/li&gt;&lt;li&gt;Clear contact information&lt;/li&gt;&lt;li&gt;Online booking or quote request options&lt;/li&gt;&lt;li&gt;List of services offered in the navigation menu and footer&lt;/li&gt;&lt;li&gt;About Us page&lt;/li&gt;&lt;li&gt;Testimonials and reviews&lt;/li&gt;&lt;li&gt;FAQ section&lt;/li&gt;&lt;li&gt;Blog or resources section with localized content&lt;/li&gt;&lt;li&gt;Local SEO optimization, including city pages and local service area content&lt;/li&gt;&lt;li&gt;Social media integration&lt;/li&gt;&lt;li&gt;Security features&lt;/li&gt;&lt;li&gt;Easy navigation&lt;/li&gt;&lt;/ol&gt;&lt;h3&gt;How can I make my plumbing website more mobile-friendly?&lt;/h3&gt;&lt;p&gt;To make your plumbing website more mobile-friendly, ensure it has a responsive design that adjusts to different screen sizes, optimize images and content for faster loading times on mobile devices, and ensure buttons and links are easy to tap on a touchscreen.&lt;/p&gt;&lt;p&gt;Professional plumbing website design firms can assist in choosing the best plumber website builder and themes for mobile. WordPress is a prominent CMS platform that can be customized to your plumbing company’s needs.&lt;/p&gt;&lt;h3&gt;What are the benefits of having an online booking system on my plumbing website?&lt;/h3&gt;&lt;p&gt;An online booking system can streamline the appointment scheduling process, making it more convenient for customers to book services at any time.&lt;/p&gt;&lt;p&gt;A variety of options, including phone numbers, chatbots, email forms, and online booking plugins, can reduce administrative workload, improve efficiency, and potentially increase the number of bookings by offering a user-friendly experience.&lt;/p&gt;&lt;p&gt;Plumbing websites are lead generation tools. Offering multiple forms of contact meets user preferences.&lt;/p&gt;&lt;h3&gt;How can I improve the SEO of my plumbing website?&lt;/h3&gt;&lt;p&gt;Search engine optimization (SEO) is a core element of effective plumber website design. While beautifully designed plumber websites are a great starting point, web crawlers need to understand what your website is about to rank it effectively.&lt;/p&gt;&lt;p&gt;Improving SEO focuses on increasing the relevance, credibility, and authority of your website. For a plumbing company, this means conducting local keyword research to identify relevant plumbing terms and optimizing your website’s content and meta tags with these keywords.&lt;/p&gt;&lt;p&gt;Advanced SEO involves creating high-quality and informative localized content, building backlinks from reputable sources, and regularly updating your content to keep it fresh and aligned with search engine updates.&lt;/p&gt;&lt;p&gt;The post &lt;a href=&quot;https://valveandmeter.com/blog/web/best-plumbing-websites/&quot;&gt;15 Features Of The Best Plumbing Websites With Examples&lt;/a&gt; appeared first on &lt;a href=&quot;https://valveandmeter.com&quot;&gt;Valve + Meter&lt;/a&gt;.&lt;/p&gt;</content:encoded>
</item>
<item>
<title>Link + Popover Navigation</title>
<link>https://adrianroselli.com/2026/07/link-popover-navigation.html</link>
<guid isPermaLink="false">lDOPnwdTCyMA77s_1FfDV1QPP15vSOAQ_-qb7g==</guid>
<pubDate>Wed, 15 Jul 2026 19:53:18 +0000</pubDate>
<description>This is a redress of my 2019 post Link + Disclosure Widget Navigation, except (as the title implies), I’ve modified it to use native HTML popovers instead of ARIA or HTML disclosure widgets. Popover has the benefit of using appropriate HTML structure and semantics while removing the need for scripting…</description>
<content:encoded>&lt;p&gt;
This is a &lt;a href=&quot;https://en.wikipedia.org/wiki/Set_redress&quot;&gt;redress&lt;/a&gt; of my 2019 post &lt;a href=&quot;https://adrianroselli.com/2019/06/link-disclosure-widget-navigation.html&quot;&gt;&lt;cite&gt;Link + Disclosure Widget Navigation&lt;/cite&gt;&lt;/a&gt;, except (as the title implies), I’ve modified it to use native HTML popovers instead of ARIA or HTML disclosure widgets.
&lt;/p&gt;&lt;p&gt;
Popover has the benefit of using appropriate HTML structure and semantics while removing the need for scripting and ARIA. I use some ARIA here regardless.
&lt;/p&gt;&lt;h2&gt;&lt;code&gt;popover&lt;/code&gt; vs. Disclosure Widgets&lt;/h2&gt;&lt;p&gt;
ARIA-based disclosure widgets (&lt;code&gt;&amp;lt;button aria-expanded&amp;gt;&lt;/code&gt;) require script to work. HTML-based disclosure widgets (&lt;code&gt;&amp;lt;details&amp;gt;&lt;/code&gt; / &lt;code&gt;&amp;lt;summary&amp;gt;&lt;/code&gt;) expand when there is a hit for in-page searches (in two engines), have inconsistent exposure to accessibility APIs (across browsers and over time), and in my experience confuddle teams with styling overrides. I have a &lt;a href=&quot;https://adrianroselli.com/2020/05/disclosure-widgets.html&quot;&gt;2020 post going into some detail&lt;/a&gt; on each.
&lt;/p&gt;&lt;p&gt;
The &lt;a href=&quot;https://html.spec.whatwg.org/multipage/popover.html#the-popover-attribute&quot;&gt;&lt;code&gt;popover&lt;/code&gt;&lt;/a&gt; feature requires CSS for positioning. Cross-browser &lt;a href=&quot;https://caniuse.com/?search=anchor+positioning&quot;&gt;anchor positioning support&lt;/a&gt; only came about recently. Because &lt;code&gt;popover&lt;/code&gt;s appear on the top layer, you have to be careful with anything that uses &lt;co0de&gt;z-index. Only use this pattern if all the dialogs on your site are already using the native HTML &lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; element, &lt;a href=&quot;https://adrianroselli.com/2023/05/brief-note-on-popovers-with-dialogs.html&quot;&gt;otherwise overlapping issues&lt;/a&gt; happen.
&lt;/co0de&gt;&lt;/p&gt;&lt;h2&gt;The Pattern&lt;/h2&gt;&lt;p&gt;
Embedded below or &lt;a href=&quot;https://codepen.io/aardrian/pen/dPNJgKQ&quot;&gt;visit it directly at Codepen&lt;/a&gt;. There’s also a &lt;a href=&quot;https://cdpn.io/aardrian/debug/dPNJgKQ/&quot;&gt;debug version&lt;/a&gt;.
&lt;/p&gt;&lt;p&gt;&lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/aardrian/pen/dPNJgKQ&quot;&gt;
  Link + Popover Nav&lt;/a&gt; by Adrian Roselli (&lt;a href=&quot;https://codepen.io/aardrian&quot;&gt;@aardrian&lt;/a&gt;)
  on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;&lt;/p&gt;&lt;h3&gt;The Container&lt;/h3&gt;&lt;p&gt;
The navigation lives in a &lt;code&gt;&amp;lt;nav&amp;gt;&lt;/code&gt; as a list with nested lists.
&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&amp;lt;header&amp;gt;
  &amp;lt;nav id=&amp;quot;Nav&amp;quot;&amp;gt;
    &amp;lt;ul&amp;gt;
    […]
    &amp;lt;/ul&amp;gt;
  &amp;lt;/nav&amp;gt;
&amp;lt;/header&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;The Links&lt;/h3&gt;&lt;p&gt;
The links are links. They work with a keyboard, assistive technology knows what to do with them, and links are generally well understood by users.
&lt;/p&gt;&lt;p&gt;
I identify that the current parent page is the &lt;em&gt;About&lt;/em&gt; page and the current page is &lt;em&gt;Job Postings&lt;/em&gt; by using &lt;code&gt;aria-current=&amp;quot;page&amp;quot;&lt;/code&gt;. I also use that as my CSS selector and &lt;a href=&quot;https://adrianroselli.com/2021/06/using-css-to-enforce-accessibility.html&quot;&gt;you should too&lt;/a&gt;.
&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&amp;lt;li&amp;gt;
  &amp;lt;a href=&amp;quot;[…]&amp;quot; id=&amp;quot;Item02&amp;quot; aria-current=&amp;quot;page&amp;quot;&amp;gt;About&amp;lt;/a&amp;gt;
  […]
  &amp;lt;ul id=&amp;quot;SubItem02&amp;quot; popover&amp;gt;
    […]
    &amp;lt;li&amp;gt;&amp;lt;a href=&amp;quot;[…]&amp;quot; aria-current=&amp;quot;page&amp;quot;&amp;gt;Job Postings&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
  &amp;lt;/ul&amp;gt;
&amp;lt;/li&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;The Button Trigger&lt;/h3&gt;&lt;p&gt;
As my older pattern, the &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; element is the trigger. Unlike my older pattern, it doesn’t use &lt;code&gt;aria-expanded&lt;/code&gt; (nor &lt;code&gt;aria-controls&lt;/code&gt;, which was unnecessary then anyway).
&lt;/p&gt;&lt;p&gt;
Instead it uses &lt;a href=&quot;https://html.spec.whatwg.org/multipage/popover.html#the-popover-target-attributes&quot;&gt;&lt;code&gt;popovertarget&lt;/code&gt;&lt;/a&gt;, which brings the programmatic state &lt;code&gt;aria-expanded&lt;/code&gt; for free. The value of &lt;code&gt;popovertarget&lt;/code&gt; must be the &lt;code&gt;id&lt;/code&gt; of the content whose appearance you want to control (the popover content). The popover content needs the &lt;code&gt;popover&lt;/code&gt; attribute for this to work.
&lt;/p&gt;&lt;p&gt;
No CSS or JavaScript is needed to make this content appear or disappear.
&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&amp;lt;button type=&amp;quot;button&amp;quot; id=&amp;quot;btnItem02&amp;quot; aria-labelledby=&amp;quot;Item02&amp;quot; popovertarget=&amp;quot;SubItem02&amp;quot;&amp;gt;
  &amp;lt;svg […]&amp;gt;[…]&amp;lt;/svg&amp;gt;
&amp;lt;/button&amp;gt;
&amp;lt;ul id=&amp;quot;SubItem02&amp;quot;popover&amp;gt;
 […]
&amp;lt;/ul&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Naming the Button&lt;/h3&gt;&lt;p&gt;
This is the second place I still use ARIA. To &lt;a href=&quot;https://adrianroselli.com/2020/01/my-priority-of-methods-for-labeling-a-control.html&quot;&gt;avoid text duplication and auto-translation issues&lt;/a&gt;, I use &lt;code&gt;aria-labelledby&lt;/code&gt; to reference the text of the preceding link. Screen reader users hear / feel a different control type and that it’s expandable, which has proven to be sufficient context. For voice users, saying the visible text presents them with the option to choose the link or the button.
&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&amp;lt;a href=&amp;quot;[…]&amp;quot; id=&amp;quot;Item02&amp;quot; aria-current=&amp;quot;page&amp;quot;&amp;gt;About&amp;lt;/a&amp;gt;
&amp;lt;button type=&amp;quot;button&amp;quot; id=&amp;quot;btnItem02&amp;quot; aria-labelledby=&amp;quot;Item02&amp;quot; popovertarget=&amp;quot;SubItem02&amp;quot;&amp;gt;
  &amp;lt;svg […]&amp;gt;[…]&amp;lt;/svg&amp;gt;
&amp;lt;/button&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Focus Order Support&lt;/h3&gt;&lt;p&gt;
You must &lt;a href=&quot;https://html.spec.whatwg.org/multipage/popover.html#the-popover-attribute:~:text=Whenever%20possible%20ensure%20the%20popover%20element%20is%20placed%20immediately%20after%20its%20triggering%20element%20in%20the%20DOM&quot;&gt;place the popover content immediately after the popover trigger&lt;/a&gt;, otherwise you risk reading order and focus order problems for users. The popover feature does not automatically do any focus management.
&lt;/p&gt;&lt;h3&gt;&lt;kbd&gt;Esc&lt;/kbd&gt; Support&lt;/h3&gt;&lt;p&gt;
It comes for free with popover.
&lt;/p&gt;&lt;h3&gt;Click-to-Close Support&lt;/h3&gt;&lt;p&gt;
It comes for free with popover.
&lt;/p&gt;&lt;h3&gt;Focusout-to-Close Support&lt;/h3&gt;&lt;p&gt;
You’ll have to write your own.
&lt;/p&gt;&lt;h3&gt;Styles&lt;/h3&gt;&lt;p&gt;
Not all of these styles will work for you, but these may matter:
&lt;/p&gt;&lt;ul&gt;&lt;li&gt;I use &lt;a href=&quot;https://adrianroselli.com/2019/11/css-logical-properties.html&quot;&gt;logical properties&lt;/a&gt; throughout. The demo has buttons to let you cycle between four writing directions. They are not a perfect test since neither the rest of the page nor the text change.&lt;/li&gt;&lt;li&gt;There are no classes. IDs only exist as hooks for popover and naming  references.&lt;/li&gt;&lt;li&gt;the links are &lt;a href=&quot;https://adrianroselli.com/2020/02/block-links-cards-clickable-regions-etc.html&quot;&gt;block links&lt;/a&gt; (using CSS generated content) to fill the container. The buttons have a &lt;code&gt;z-index&lt;/code&gt; to sit in front of them.&lt;/li&gt;&lt;li&gt;The layout uses flexbox with no width media queries. If you don’t like the wrap or the layout, write your own.&lt;/li&gt;&lt;li&gt;Supports forced-colors / Windows High Contrast Mode out of the box with no custom CSS. That’s partly because the focus styles use outlines.&lt;/li&gt;&lt;li&gt;Uses &lt;code&gt;color-scheme: light dark&lt;/code&gt; just so I could more easily see styling gaps, but I don’t recommend that for real-life use.&lt;/li&gt;&lt;li&gt;The buttons use an arrow SVG that rotates via &lt;a href=&quot;https://drafts.csswg.org/selectors/#selectordef-popover-open&quot;&gt;&lt;code&gt;:popover-open&lt;/code&gt;&lt;/a&gt; when the popover appears / disappears. It honors user motion preferences.&lt;/li&gt;&lt;li&gt;The button and arrow position shift based on typeface, as with any layout mixing icons and type.&lt;/li&gt;&lt;li&gt;The popover triggers conform to &lt;a href=&quot;https://www.w3.org/WAI/WCAG22/Understanding/target-size-minimum.html&quot;&gt;2.5.8 Target Size (Minimum)&lt;/a&gt;, but you can always make them bigger. Make sure yours at least conform.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;
There is a button on the demo to remove all styles from the page, making it easier to see how this will perform as raw HTML, for easy testing, or for &lt;a href=&quot;https://css-naked-day.org/&quot;&gt;CSS Naked Day&lt;/a&gt;.
&lt;/p&gt;&lt;h4&gt;Popover Positioning&lt;/h4&gt;&lt;p&gt;
I haven’t played with popovers much. Positioning it to work across Firefox, Chromium, and Safari while also not overflowing the window or falling apart with text direction changes proved to be a challenge. I welcome suggestions on improvements.
&lt;/p&gt;&lt;p&gt;
My goal was to have the popover appear to the right edge of the parent list item and just below the button — for left-to-right languages  — and in a corresponding location for other language text directions.
&lt;/p&gt;&lt;pre&gt;&lt;code&gt;[popover] {
  inset: auto;
  position-anchor: auto;
  inset-block-start: anchor(self-end);
  inset-inline-end: calc(anchor(self-end) - 1em);
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;
I flailed about trying different values from the spec and struggled to understand logical properties with anchor positioning. As it is, the popover positioning falls apart with the vertical writing modes, but I can’t be sure if that’s my code or browser bugs (&lt;span&gt;¿Por qué no los dos?&lt;/span&gt;).
&lt;/p&gt;&lt;h2&gt;Pattern History&lt;/h2&gt;&lt;p&gt;
There are reasons I built this pattern.
&lt;/p&gt;&lt;p&gt;
Early in 2017 I &lt;a href=&quot;https://github.com/w3c/aria-practices/issues/353&quot;&gt;filed an issue against ARIA Authoring Practices&lt;/a&gt; (APG) requesting a change to the menu navigation pattern. Despite a great deal of feedback in agreement, it continues to languish. In late 2017 I wrote &lt;a href=&quot;https://adrianroselli.com/2017/10/dont-use-aria-menu-roles-for-site-nav.html&quot;&gt;&lt;cite&gt;Don’t Use ARIA Menu Roles for Site Nav&lt;/cite&gt;&lt;/a&gt; and started actively campaigning against the APG pattern. In &lt;a href=&quot;https://github.com/w3c/aria-practices/pull/1036&quot;&gt;2019, Sarah Higley proposed&lt;/a&gt; a &lt;a href=&quot;https://www.w3.org/WAI/ARIA/apg/patterns/disclosure/examples/disclosure-navigation/&quot;&gt;disclosure-only APG pattern&lt;/a&gt; and &lt;a href=&quot;https://www.w3.org/WAI/ARIA/apg/patterns/disclosure/examples/disclosure-navigation-hybrid/&quot;&gt;then a disclosure and link&lt;/a&gt; pattern &lt;a href=&quot;https://github.com/w3c/aria-practices/pull/1614&quot;&gt;proposal in 2020&lt;/a&gt;.
&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://adrianroselli.com/wp-content/uploads/2026/07/link-popover-nav_thumb.jpg&quot; alt=&quot;A plate full of slightly under-baked mini popovers, or tiny Yorkshire puddings, with a web site navigation over the top and a similar vertical navigation down the left.&quot; title=&quot;&quot;/&gt;&lt;figcaption&gt;An under-baked batch of mini popovers I made for my solo Easter during the first year of the pandemic. I still ate them all.&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;
HTML (via Open-UI) has since stepped in to fill some gaps, so here we are.
&lt;/p&gt;&lt;h2&gt;Wrap Up&lt;/h2&gt;&lt;p&gt;
Standard disclaimers apply: may not work for your audience, likely has bugs, support can change, new features may moot this, my design sense is poor, yes you have to write script to get whatever custom interactions you think are nifty but which probably annoy users, who even writes code anymore, using [insert random element here] in a way that’s not mentioned in this post is your problem not mine, etc.
&lt;/p&gt;&lt;p&gt;
And if you have more experience with multilingual popover anchor positioning, please share!
&lt;/p&gt;&lt;h2&gt;Update: Same Day&lt;/h2&gt;&lt;p&gt;
Ana Tudor, &lt;a href=&quot;https://bsky.app/profile/anatudor.bsky.social/post/3mq7stovxy22u&quot;&gt;possibly offended by my middling layout skills and crap SVG knowledge&lt;/a&gt;, forked my demo to replace the block link with good old-fashioned grid, make a better SVG, adjust the popovers to stay in the viewport for vertical RtL layouts, and address the rotation differently (you’ll need to add language code selectors for your use cases).
&lt;/p&gt;&lt;p&gt;
I plan to test and integrate them into mine, but until then I’ve embedded &lt;a href=&quot;https://codepen.io/thebabydino/pen/gbgvZEz&quot;&gt;Ana’s fork&lt;/a&gt; or you can visit the &lt;a href=&quot;https://cdpn.io/thebabydino/debug/gbgvZEz&quot;&gt;debug view of Ana’s fork&lt;/a&gt; directly.
&lt;/p&gt;&lt;p&gt;&lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/thebabydino/pen/gbgvZEz&quot;&gt;
  Link + Popover Nav&lt;/a&gt; by Ana Tudor (&lt;a href=&quot;https://codepen.io/thebabydino&quot;&gt;@thebabydino&lt;/a&gt;)
  on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;
Curtis Wilcox made a &lt;a href=&quot;https://codepen.io/ccwilcox/pen/rajJPdy&quot;&gt;fork with the anchor positioning polyfill&lt;/a&gt;. I intentionally ignored the polyfill because I didn’t want to field tech support questions for a thing I haven’t used, but Curtis is a better than man than I. I’m still not tech support for the polyfill (&lt;a href=&quot;https://bsky.app/profile/jamessw.com/post/3mqasamhqvc2l&quot;&gt;James Stuckey Weber might be&lt;/a&gt;, but probably don’t pester him).
&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://sunny.garden/@knowler/116893002975078967&quot;&gt;Nathan Knowler reminded me&lt;/a&gt; that &lt;code&gt;:not(:open)::details-content&lt;/code&gt; can be used to prevent a &lt;code&gt;&amp;lt;details&amp;gt;&lt;/code&gt; from expanding on a hit from in-page search but since I was too lazy to test it for this post, &lt;a href=&quot;https://knowler.dev/demos/wYUnfPQ?codepen&quot;&gt;go check out his demo&lt;/a&gt;.
&lt;/p&gt;&lt;h2&gt;Update: 10 July 2026&lt;/h2&gt;&lt;p&gt;
Technically still the same day, but I was already away from screens and am only adding it today. Kirk Loretz did &lt;em&gt;not&lt;/em&gt; fork my pen, but he made a new one to &lt;a href=&quot;https://codepen.io/kirk-l/pen/dPNdBLy&quot;&gt;test popover positioning with different writing modes&lt;/a&gt;. Then he went to the CSS Working Group repo and filed &lt;a href=&quot;https://github.com/w3c/csswg-drafts/issues/14159&quot;&gt;#14159 [css-anchor-position-1] Popover text direction/writing-mode bugs&lt;/a&gt;. If you have expertise in this area, it might be worth checking the issue.
&lt;/p&gt;</content:encoded>
</item>
<item>
<title>Focusgroup Tests — Adrian Roselli</title>
<link>https://adrianroselli.com/2026/07/focusgroup-tests.html</link>
<enclosure type="image/jpeg" length="0" url="https://adrianroselli.com/wp-content/uploads/2026/07/focusgroup-tests_thumb-300x300.jpg"></enclosure>
<guid isPermaLink="false">ZB0dkUSa6Rrwm3P3-FHoLU_NQSQ77LN5OVNagQ==</guid>
<pubDate>Wed, 15 Jul 2026 19:53:18 +0000</pubDate>
<description>Chrome 150 has landed support for focusgroup, a feature proposed by Open-UI and not yet in WHATWG HTML as anything more than a feature request. Open-UI has outsized representation from Google and Microsoft folks, so it’s no surprise Chrome would implement it first. “FOCUS!” by Metro Centric, CC BY 2.0;…</description>
<content:encoded>&lt;h1&gt;Focusgroup Tests&lt;/h1&gt;&lt;p&gt;
&lt;a href=&quot;https://developer.chrome.com/blog/new-in-chrome-150/#focusgroup&quot;&gt;Chrome 150 has landed support for &lt;code&gt;focusgroup&lt;/code&gt;&lt;/a&gt;, a feature proposed by Open-UI and not yet in WHATWG HTML as anything more than a &lt;a href=&quot;https://github.com/whatwg/html/issues/11641&quot;&gt;feature request&lt;/a&gt;. Open-UI has outsized representation from Google and Microsoft folks, so it’s no surprise Chrome would implement it first.
&lt;/p&gt;&lt;figure&gt;
 &lt;img src=&quot;https://adrianroselli.com/wp-content/uploads/2026/07/focusgroup-tests_thumb.jpg&quot; alt=&quot;“FOCUS!” set in a stylized geometric font and in stone the color of sand, two characters per line and each maybe ten feet tall, mounted on a wall of similar stone slabs, but the slabs are completely desaturated.&quot; title=&quot;&quot;/&gt;
 &lt;figcaption&gt;“&lt;a href=&quot;https://www.flickr.com/photos/16782093@N03/9118080939&quot;&gt;FOCUS!&lt;/a&gt;” by &lt;a href=&quot;https://www.flickr.com/photos/16782093@N03/&quot;&gt;Metro Centric&lt;/a&gt;, &lt;a href=&quot;https://creativecommons.org/licenses/by/2.0/deed.en&quot;&gt;CC BY 2.0&lt;/a&gt;; cropped, skewed, and desaturated.&lt;/figcaption&gt;
&lt;/figure&gt;&lt;p&gt;
Open-UI has a handy &lt;a href=&quot;https://open-ui.org/components/scoped-focusgroup.explainer/&quot;&gt;&lt;code&gt;focusgroup&lt;/code&gt; explainer&lt;/a&gt;, which is the only place (inside or outside of a spec) to understand how to use it and what it’s meant to do. Which means this is your best opportunity to play with it before it gets into a spec.
&lt;/p&gt;&lt;p&gt;
To hopefully make that easier, I’ve created a series of very basic demos using the &lt;a href=&quot;https://open-ui.org/components/scoped-focusgroup.explainer/#focusgroup-tokens&quot;&gt;properties outlined in the explainer&lt;/a&gt;.
&lt;/p&gt;&lt;h2&gt;Demos&lt;/h2&gt;&lt;p&gt;
There is no scripting in any of these demos. The only styles are to honor dark / light mode, make focus styles more obvious, and add some text defaults. None of the controls do anything. The labels are nonsense. I left a set of boring default HTML buttons at the end of each demo as a comparison.
&lt;/p&gt;&lt;p&gt;
Observe how the axis of movement is based on the default widget visual display. For example, &lt;code&gt;listbox&lt;/code&gt; wants up and down arrows because they are generally vertically stacked (block). My demo, however, makes no effort to set a layout. This is not a &lt;code&gt;focusgroup&lt;/code&gt; bug, but if you use &lt;code&gt;listbox&lt;/code&gt; for something like “pills,” which are generally horizontal (inline), then you would need to address that.
&lt;/p&gt;&lt;p&gt;
With all the available properties, there are more permutations than I have coded. I also haven’t done complex nor nested demos. I just don’t have the time. But you, dear reader, probably enjoy pointing out things I missed. Please do so. Make your own examples. Play around. Try your own use cases and expectations. Leave comments letting me know what I, or the proposal, got wrong.
&lt;/p&gt;&lt;p&gt;
If you work in digital accessibility, you have an opportunity to find gaps that others might miss (especially in how roles are exposed). Let’s get those documented before the spec or browsers ossify code that cannot be readily changed in the future. Let’s help ensure developers won’t create more &lt;em&gt;inaccessible&lt;/em&gt; experiences simply by using this new property.
&lt;/p&gt;&lt;p&gt;
You’ll need Chrome 150 to test these today, but as more browsers add support these will still be useful.
&lt;/p&gt;&lt;h3&gt;&lt;code&gt;listbox&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;
&lt;a href=&quot;https://codepen.io/aardrian/pen/ogBpWWP?editors=1000&quot;&gt;View the listbox demo pen&lt;/a&gt; directly or open the &lt;a href=&quot;https://cdpn.io/aardrian/debug/ogBpWWP&quot;&gt;listbox demo in debug mode&lt;/a&gt;.
&lt;/p&gt;&lt;p&gt;
  &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/aardrian/pen/ogBpWWP/f9fc0c60d68e15bb65d473b4b04a7997&quot;&gt;
  focusgroup=&amp;quot;toolbar&amp;quot;&lt;/a&gt; by Adrian Roselli (&lt;a href=&quot;https://codepen.io/aardrian&quot;&gt;@aardrian&lt;/a&gt;)
  on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
&lt;/p&gt;&lt;h3&gt;&lt;code&gt;menu&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;
&lt;/p&gt;&lt;p&gt;
&lt;a href=&quot;https://codepen.io/aardrian/pen/EaZomoQ?editors=1000&quot;&gt;View the menu demo pen&lt;/a&gt; directly or open the &lt;a href=&quot;https://cdpn.io/aardrian/debug/EaZomoQ&quot;&gt;menu demo in debug mode&lt;/a&gt;.
&lt;/p&gt;&lt;p&gt;
  &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/aardrian/pen/EaZomoQ/5b608ff2d793e6297be3c23feaaf6683&quot;&gt;
  focusgroup=&amp;quot;menu&amp;quot;&lt;/a&gt; by Adrian Roselli (&lt;a href=&quot;https://codepen.io/aardrian&quot;&gt;@aardrian&lt;/a&gt;)
  on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
&lt;/p&gt;&lt;h3&gt;&lt;code&gt;menubar&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;
&lt;/p&gt;&lt;p&gt;
&lt;a href=&quot;https://codepen.io/aardrian/pen/wBgpdmK?editors=1000&quot;&gt;View the menubar demo pen&lt;/a&gt; directly or open the &lt;a href=&quot;https://cdpn.io/aardrian/debug/wBgpdmK&quot;&gt;menubar demo in debug mode&lt;/a&gt;.
&lt;/p&gt;&lt;p&gt;
  &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/aardrian/pen/wBgpdmK/052c22b6112aa2f587d44c2863da2e50&quot;&gt;
  focusgroup=&amp;quot;menubar&amp;quot;&lt;/a&gt; by Adrian Roselli (&lt;a href=&quot;https://codepen.io/aardrian&quot;&gt;@aardrian&lt;/a&gt;)
  on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
&lt;/p&gt;&lt;h3&gt;&lt;code&gt;radiogroup&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;
&lt;/p&gt;&lt;p&gt;
&lt;a href=&quot;https://codepen.io/aardrian/pen/ogBpWdQ?editors=1000&quot;&gt;View the radiogroup demo pen&lt;/a&gt; directly or open the &lt;a href=&quot;https://cdpn.io/aardrian/debug/ogBpWdQ&quot;&gt;radiogroup demo in debug mode&lt;/a&gt;.
&lt;/p&gt;&lt;p&gt;
  &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/aardrian/pen/ogBpWdQ/23d82ab15afecf34321deca513b4c612&quot;&gt;
  focusgroup=&amp;quot;radiogroup&amp;quot;&lt;/a&gt; by Adrian Roselli (&lt;a href=&quot;https://codepen.io/aardrian&quot;&gt;@aardrian&lt;/a&gt;)
  on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
&lt;/p&gt;&lt;h3&gt;&lt;code&gt;tablist&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;
&lt;/p&gt;&lt;p&gt;
&lt;a href=&quot;https://codepen.io/aardrian/pen/RNKxVBz?editors=1000&quot;&gt;View the tablist demo pen&lt;/a&gt; directly or open the &lt;a href=&quot;https://cdpn.io/aardrian/debug/RNKxVBz&quot;&gt;tablist demo in debug mode&lt;/a&gt;.
&lt;/p&gt;&lt;p&gt;
  &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/aardrian/pen/RNKxVBz/4e73d5112fbe5df7c9b1f197d093495a&quot;&gt;
  focusgroup=&amp;quot;tablist&amp;quot;&lt;/a&gt; by Adrian Roselli (&lt;a href=&quot;https://codepen.io/aardrian&quot;&gt;@aardrian&lt;/a&gt;)
  on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
&lt;/p&gt;&lt;h3&gt;&lt;code&gt;toolbar&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;
&lt;/p&gt;&lt;p&gt;
&lt;a href=&quot;https://codepen.io/aardrian/pen/dPNJGJV?editors=1000&quot;&gt;View the toolbar demo pen&lt;/a&gt; directly or open the &lt;a href=&quot;https://cdpn.io/aardrian/debug/dPNJGJV&quot;&gt;toolbar demo in debug mode&lt;/a&gt;.
&lt;/p&gt;&lt;p&gt;
  &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/aardrian/pen/dPNJGJV/eeed2f7219f93f185ca7bc734c9563b7&quot;&gt;
  Untitled&lt;/a&gt; by Adrian Roselli (&lt;a href=&quot;https://codepen.io/aardrian&quot;&gt;@aardrian&lt;/a&gt;)
  on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
&lt;/p&gt;&lt;h2&gt;Notes&lt;/h2&gt;&lt;p&gt;
In very quick tests, a few things jumped out at me:
&lt;/p&gt;&lt;ul&gt;
 &lt;li&gt;In the &lt;code&gt;focusgroup=&amp;quot;none&amp;quot;&lt;/code&gt; test across all patterns, I found the button with &lt;code&gt;focusgroup=&amp;quot;none&amp;quot;&lt;/code&gt; as well as those flanking it accepted focus from &lt;kbd&gt;Tab&lt;span&gt;↹&lt;/span&gt;&lt;/kbd&gt;. It presents as the first three buttons accepting focus (the second one has &lt;code&gt;focusgroup=&amp;quot;none&amp;quot;&lt;/code&gt;). I understand why, but that feels like a bug.&lt;/li&gt;
 &lt;li&gt;The interactive child with &lt;code&gt;focusgroup=&amp;quot;none&amp;quot;&lt;/code&gt; (to remove it from the &lt;code&gt;focusgroup&lt;/code&gt;) keeps its role from the element, instead of assigning it the required accessibility child role. That might feel like a bug if you expect &lt;code&gt;focusgroup=&amp;quot;none&amp;quot;&lt;/code&gt; to work like a weird disabled state (it’s not).&lt;/li&gt;
 &lt;li&gt;Adding &lt;code&gt;role=&amp;quot;presentation&amp;quot;&lt;/code&gt; to a node with &lt;code&gt;focusgroup&lt;/code&gt; has no effect. The role from the &lt;code&gt;focusgroup&lt;/code&gt; is still exposed in the accessibility tree and AAPIs. This is also true for &lt;code&gt;role=&amp;quot;none&amp;quot;&lt;/code&gt; (&lt;a href=&quot;https://w3c.github.io/aria/#presentation&quot;&gt;&lt;code&gt;none&lt;/code&gt; is a synonym for &lt;code&gt;presentation&lt;/code&gt;&lt;/a&gt;). This means you cannot apply &lt;code&gt;focusgroup&lt;/code&gt; and then un-role it to keep the interaction but avoid the other requirements of the role. This feels like a bug?&lt;/li&gt;
 &lt;li&gt;&lt;a href=&quot;https://dragonscave.space/@jscholes/116873319068797987&quot;&gt;James Scholes has corrected my misunderstanding&lt;/a&gt;. &lt;s&gt;When using NVDA or JAWS, the virtual cursor will not let me go letter-by-letter within a button (handy for getting the spelling) unless I leave forms mode (by pressing &lt;kbd&gt;Esc&lt;/kbd&gt; in JAWS, for example). This is different from how I can typically interact with buttons. That feels buggy but I am not a daily screen reader user.&lt;/s&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;
A table tracking what property behaviors are exposed from my tests.
&lt;/p&gt;&lt;h2&gt;Opinions&lt;/h2&gt;&lt;p&gt;
I’m frustrated the explainer leans on APG patterns as if they are a, or &lt;em&gt;the&lt;/em&gt;, standard while pointing only to MDN instead of the &lt;em&gt;actual&lt;/em&gt; ARIA standard. I understand why, but you may know &lt;a href=&quot;https://adrianroselli.com/2019/02/uncanny-a11y.html#APG&quot;&gt;how I feel about APG&lt;/a&gt;.
&lt;/p&gt;&lt;p&gt;
I’m frustrated the menu example refers to web site navigation, when there is &lt;a href=&quot;https://adrianroselli.com/2017/10/dont-use-aria-menu-roles-for-site-nav.html&quot;&gt;years-old evidence showing that as an anti-pattern&lt;/a&gt;. I’m working on a web site navigation post to address this. &lt;em&gt;14 July&lt;/em&gt;: Luke Warlow replaced the site navigation example with a far more appropriate toolbar example. &lt;a href=&quot;https://github.com/openui/open-ui/issues/1474&quot;&gt;Issue 1474&lt;/a&gt;, &lt;a href=&quot;https://github.com/openui/open-ui/pull/1475&quot;&gt;PR 1475&lt;/a&gt;.
&lt;/p&gt;&lt;p&gt;
I’m frustrated yet &lt;a href=&quot;https://adrianroselli.com/2020/07/aria-grid-as-an-anti-pattern.html&quot;&gt;pleased&lt;/a&gt; navigation across a two-axis construct, such as an ARIA grid, is deferred. Though I understand that gets more complex.
&lt;/p&gt;&lt;p&gt;
While I’m thrilled this isn’t going into CSS, I’m worried that it isn’t completely off the table. I’ve so far been &lt;a href=&quot;https://alice.boxhall.au/articles/a-threat-model-for-accessibility-on-the-web/&quot;&gt;unimpressed with the CSS Working Group’s ability to address accessibility concerns&lt;/a&gt;.
&lt;/p&gt;&lt;p&gt;
I’m excited to see these ARIA concepts finally make it to HTML (if only as a proposal so far). This fits the original model of ARIA as a temporary, bridging technology until HTML caught up. Which hey, maybe that will start to happen.
&lt;/p&gt;&lt;p&gt;
While I like that using &lt;code&gt;focusgroup&lt;/code&gt; brings ARIA roles for free (in the accessibility tree and exposed to AAPIs), I’m worried how readily developers will unintentionally &lt;a href=&quot;https://adrianroselli.com/2020/02/role-up.html&quot;&gt;role-up&lt;/a&gt; standard HTML just to get free arrow key interactions. One Open-UI explainer &lt;a href=&quot;https://open-ui.org/components/scoped-focusgroup.explainer/#nested-focusgroups&quot;&gt;menu example fails to remove roles from lists&lt;/a&gt;, demonstrating how easy it is to forget unless your testing is robust ( &lt;em&gt;14 July&lt;/em&gt;: &lt;a href=&quot;https://github.com/openui/open-ui/issues/1476&quot;&gt;issue 1476&lt;/a&gt; should address this).
&lt;/p&gt;&lt;p&gt;
In April, Steve Frenzel shared his own in &lt;cite&gt;&lt;a href=&quot;https://www.stevefrenzel.dev/posts/my-thoughts-on-the-focusgroup-attribute-proposal/&quot;&gt;My thoughts on the “focusgroup” attribute proposal&lt;/a&gt;&lt;/cite&gt; for an older proposal.
&lt;/p&gt;&lt;h2&gt;Added to Link + Disclosure Nav&lt;/h2&gt;&lt;p&gt;
&lt;em&gt;6 July 2026&lt;/em&gt;. Open-UI’s &lt;code&gt;focusgroup&lt;/code&gt; &lt;a href=&quot;https://open-ui.org/components/scoped-focusgroup.explainer/#quickstart&quot;&gt;&lt;em&gt;Quickstart&lt;/em&gt; (incorrectly) uses the APG &lt;code&gt;menu&lt;/code&gt; navigation pattern&lt;/a&gt;. In mid-2019 I responded to the APG pattern with a &lt;a href=&quot;https://adrianroselli.com/2019/06/link-disclosure-widget-navigation.html#Pattern&quot;&gt;link &amp;amp; disclosure navigation prototype&lt;/a&gt;. Today &lt;a href=&quot;https://codepen.io/aardrian/pen/myRpKpg?editors=1000&quot;&gt;I forked it&lt;/a&gt; (&lt;a href=&quot;https://cdpn.io/aardrian/debug/myRpKpg&quot;&gt;debug view&lt;/a&gt;) and added &lt;code&gt;focusgroup=&amp;quot;menubar&amp;quot;&lt;/code&gt;, something I half expect authors to try.
&lt;/p&gt;&lt;p&gt;
  &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/aardrian/pen/myRpKpg&quot;&gt;
  2017 Link + Disclosure Nav with `focusgroup`&lt;/a&gt; by Adrian Roselli (&lt;a href=&quot;https://codepen.io/aardrian&quot;&gt;@aardrian&lt;/a&gt;)
  on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
&lt;/p&gt;&lt;p&gt;
I got some interesting results:
&lt;/p&gt;&lt;figure&gt;
 &lt;a href=&quot;https://adrianroselli.com/wp-content/uploads/2026/07/focusgroup-nav-Chrome150_focusgroup.jpg&quot;&gt;&lt;img src=&quot;https://adrianroselli.com/wp-content/uploads/2026/07/focusgroup-nav-Chrome150_focusgroup.jpg&quot; alt=&quot;Dev tools showing focusgroup=&amp;quot;menubar&amp;quot; on the &amp;lt;ul&amp;gt; and the &amp;lt;li&amp;gt; reporting its role as listitem.&quot; title=&quot;&quot;/&gt;&lt;/a&gt;
 &lt;a href=&quot;https://adrianroselli.com/wp-content/uploads/2026/07/focusgroup-nav-Chrome150_role.jpg&quot;&gt;&lt;img src=&quot;https://adrianroselli.com/wp-content/uploads/2026/07/focusgroup-nav-Chrome150_role.jpg&quot; alt=&quot;Dev tools showing role=&amp;quot;menubar&amp;quot; on the &amp;lt;ul&amp;gt; and the &amp;lt;li&amp;gt; reporting its role as none.&quot; title=&quot;&quot;/&gt;&lt;/a&gt;
 &lt;a href=&quot;https://adrianroselli.com/wp-content/uploads/2026/07/focusgroup-nav-Chrome150_focusgroup-role.jpg&quot;&gt;&lt;img src=&quot;https://adrianroselli.com/wp-content/uploads/2026/07/focusgroup-nav-Chrome150_focusgroup-role.jpg&quot; alt=&quot;Dev tools showing focusgroup=&amp;quot;menubar&amp;quot; and role=&amp;quot;menubar&amp;quot; on the &amp;lt;ul&amp;gt; and the &amp;lt;button&amp;gt; reporting its role as menuitem.&quot; title=&quot;&quot;/&gt;&lt;/a&gt;
 &lt;figcaption&gt;Using Chrome 150.&lt;/figcaption&gt;
&lt;/figure&gt;&lt;p&gt;
I don’t know if this is a bug, but it certainly feels like someplace a dev can get confused when determining where to apply ARIA roles for accessibility children of the navigation.
&lt;/p&gt;&lt;h2&gt;Update: 10 July 2026&lt;/h2&gt;&lt;p&gt;
The Microsoft Edge team is working on an explainer, &lt;a href=&quot;https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Accessibility/FocusLandmark/explainer.md&quot;&gt;Focus Landmark: declarative landmark focus navigation for web apps&lt;/a&gt;. It looks like it might using the momentum from &lt;code&gt;focusgroup&lt;/code&gt; to pitch &lt;code&gt;focuslandmark&lt;/code&gt;, possibly with similar ARIA-role-setting tokens. Discussion is already happening at Open-Ui issue &lt;a href=&quot;https://github.com/openui/open-ui/issues/1472&quot;&gt;#1472 [focuslandmark] No declarative way to move keyboard focus between major page regions&lt;/a&gt;. Sarah Higley linked to &lt;a href=&quot;https://webwewant.fyi/wants/5f24adc28dfea402c3830218/&quot;&gt;Carolyn MacLeod’s years-old Web We Want request&lt;/a&gt;.
&lt;/p&gt;&lt;h2&gt;Update: 13 July 2026&lt;/h2&gt;&lt;p&gt;
The Microsoft Edge team also has its own tests, linked from the undated &lt;a href=&quot;https://microsoftedge.github.io/Demos/focusgroup/&quot;&gt;&lt;cite&gt;Focusgroup: Interactive Demos&lt;/cite&gt;&lt;/a&gt;. They were definitely published before Chrome’s release, given it tells readers how to enable experimental flags.
&lt;/p&gt;&lt;article&gt;

			&lt;h2&gt;2 Comments&lt;/h2&gt;


			
			&lt;div&gt;

				&lt;blockquote&gt;
					&lt;div&gt;
						&lt;header&gt;
						
						
						&lt;a href=&quot;https://adrianroselli.com/2026/07/focusgroup-tests.html?replytocom=421723#respond&quot;&gt;Reply&lt;/a&gt;
						&lt;/header&gt;


					&lt;p&gt;Thanks for putting these together! That’s super useful! &lt;/p&gt;
&lt;p&gt;Question, why do you have a demo with role=”presentation”?&lt;br/&gt;
One should never have a focus group of buttons without a group level element without a role (one of listbox, menu, menubar, toolbar, radio group, or something I am forgetting).&lt;br/&gt;
Without one, a screen reader will not go into forms/application/focus mode so the arrow keys won’t work, and there is no hint to the screen reader user that these controls are grouped.&lt;/p&gt;
&lt;p&gt;On orientation, is there an attribute that goes with focusgroup to indicate orientation?&lt;br/&gt;
If not, that might be something to propose, because it impacts both how the group is presented to a screen reader user and the keyboard operability of it (with and without a screen reader).&lt;br/&gt;
Another approach would be to support all arrow keys, with arrows up/left navigate up/previous and arrows down/right navigate down/next.&lt;br/&gt;
That way the orientation becomes less important, at least from a functional perspective. You could use aria-orientation as a descriptive attribute to tell the screen reader user what it looks like, but it wouldn’t have to tie into the keyboard operation.&lt;/p&gt;
&lt;p&gt;There are still some things to figure out with all this, but it’s super exciting that finally we are getting the capability to support keyboard operation for common components, pretty much without JavaScript.&lt;/p&gt;


					&lt;/div&gt;
					&lt;footer&gt;
						&lt;cite&gt;Birkir&lt;/cite&gt;;
						&lt;time&gt;7 July 2026 at 12:24 pm&lt;/time&gt;.
						&lt;a href=&quot;https://adrianroselli.com/2026/07/focusgroup-tests.html#comment-421723&quot;&gt;Permalink&lt;/a&gt;
					&lt;/footer&gt;

				&lt;/blockquote&gt;


			&lt;div&gt;

				&lt;blockquote&gt;
					&lt;div&gt;
						&lt;header&gt;
						In response to &lt;a href=&quot;https://adrianroselli.com/2026/07/focusgroup-tests.html#comment-421723&quot;&gt;Birkir&lt;/a&gt;.
						
						&lt;a href=&quot;https://adrianroselli.com/2026/07/focusgroup-tests.html?replytocom=421727#respond&quot;&gt;Reply&lt;/a&gt;
						&lt;/header&gt;


					&lt;blockquote&gt;&lt;p&gt;
Question, why do you have a demo with role=”presentation”?
&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;Because I am confident I will run into teams in the wild trying to get free arrow key navigation in a thing without it getting roled-up into something they don’t want. I was being pre-emptive (and judgmental).&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;
On orientation, is there an attribute that goes with focusgroup to indicate orientation?
&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;The &lt;code&gt;inline&lt;/code&gt; and &lt;code&gt;block&lt;/code&gt; tokens are as close as it gets. Though they are less about orientation and more about reading axis. My tests show throwing in both tokens supports both sets (all) of arrow keys working. Throwing &lt;code&gt;aria-orientation&lt;/code&gt; had no functional impact.&lt;/p&gt;


					&lt;/div&gt;
					&lt;footer&gt;
						&lt;cite&gt;&lt;a href=&quot;https://adrianroselli.com/&quot;&gt;Adrian Roselli&lt;/a&gt;&lt;/cite&gt;;
						&lt;time&gt;7 July 2026 at 1:10 pm&lt;/time&gt;.
						&lt;a href=&quot;https://adrianroselli.com/2026/07/focusgroup-tests.html#comment-421727&quot;&gt;Permalink&lt;/a&gt;
					&lt;/footer&gt;

				&lt;/blockquote&gt;

&lt;/div&gt;
&lt;/div&gt;



		
		




	&lt;div&gt;
		&lt;h3&gt;Leave a Comment or Response &lt;small&gt;&lt;a href=&quot;https://adrianroselli.com/2026/07/focusgroup-tests.html#respond&quot;&gt;Cancel response&lt;/a&gt;&lt;/small&gt;&lt;/h3&gt;&lt;p&gt;Comment: &lt;/p&gt;&lt;ul&gt;&lt;li&gt;The form doesn’t support Markdown.&lt;/li&gt;&lt;li&gt;This form allows limited HTML.&lt;/li&gt;&lt;li&gt;Allowed HTML elements are &lt;code&gt;&amp;lt;a href&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;blockquote&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;code&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;del&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;em&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;ins&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;q&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;strong&amp;gt;&lt;/code&gt;, and maybe some others. WordPress is fickle and randomly blocks or allows some.&lt;/li&gt; &lt;li&gt;If you want to include HTML &lt;em&gt;examples&lt;/em&gt; in your comment, then HTML encode them. E.g. &lt;code&gt;&amp;lt;code&amp;gt;&amp;amp;lt;div&amp;amp;gt;&amp;lt;/code&amp;gt;&lt;/code&gt; (you can copy and paste that chunk).&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Name &lt;span&gt;*&lt;/span&gt; &lt;/p&gt;
&lt;p&gt;Email &lt;span&gt;*&lt;/span&gt; &lt;/p&gt;
&lt;p&gt;Website &lt;/p&gt;
&lt;p&gt; 

&lt;/p&gt;&lt;p&gt;Δ
&lt;/p&gt;	&lt;/div&gt;
	
		&lt;/article&gt;</content:encoded>
</item>
<item>
<title>A modern HTTP request – Nelson&#39;s log</title>
<link>https://nelsonslog.wordpress.com/2026/07/14/a-modern-http-request/</link>
<enclosure type="image/jpeg" length="0" url="https://s0.wp.com/i/blank.jpg?m=1383295312i"></enclosure>
<guid isPermaLink="false">CKUvsUzGisdtHJLbHBbFNmCsdgk_VtIdz4I8CA==</guid>
<pubDate>Wed, 15 Jul 2026 09:46:07 +0000</pubDate>
<description>I got curious about what HTTP headers Chrome is requesting so I stood up a trivial HTTP server and this is what is on the wire. $ while true; do echo -e &quot;HTTP/1.1 200 OK\nContent-Length: 3\n\n…</description>
<content:encoded>&lt;p&gt;I got curious about what HTTP headers Chrome is requesting so I stood up a trivial HTTP server and this is what is on the wire.&lt;/p&gt;&lt;div&gt;
	&lt;div&gt;
		&lt;div&gt;
			
&lt;pre&gt;&lt;code&gt;$ while true; do echo -e &amp;quot;HTTP/1.1 200 OK\nContent-Length: 3\n\nOK\n&amp;quot; | nc -l -p 8081; done

GET / HTTP/1.1
Host: localhost:8081
Connection: keep-alive
sec-ch-ua: &amp;quot;Not;A=Brand&amp;quot;;v=&amp;quot;8&amp;quot;, &amp;quot;Chromium&amp;quot;;v=&amp;quot;150&amp;quot;, &amp;quot;Google Chrome&amp;quot;;v=&amp;quot;150&amp;quot;
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: &amp;quot;Linux&amp;quot;
DNT: 1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br, zstd
Accept-Language: en-US,en;q=0.9
sec-gpc: 1
permissions-policy: browsing-topics=()&lt;/code&gt;&lt;/pre&gt;
		&lt;/div&gt;
	&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Goodness that’s a lot of stuff! I still remember when even the Host header was optional (in HTTP/0.91). And my knowledge taps out around HTTP/1.1, the &lt;code&gt;Connection: keep-alive&lt;/code&gt; and the &lt;code&gt;Accept&lt;/code&gt; headers.. Anyway, what else is there?&lt;/p&gt;&lt;p&gt;The &lt;code&gt;sec-ch-ua&lt;/code&gt; stuff is a modern replacement for &lt;code&gt;User-Agent&lt;/code&gt;&lt;/p&gt;&lt;p&gt;&lt;code&gt;DNT&lt;/code&gt;, &lt;code&gt;sec-gpc&lt;/code&gt;, and &lt;code&gt;permissions-policy&lt;/code&gt; are various requests to preserve my privacy. I just installed OptMeOwt, it is adding some of these. I think permissions-policy is a Google thing.&lt;/p&gt;&lt;p&gt;The &lt;code&gt;Sec-Fetch&lt;/code&gt; &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Sec-Fetch-Site&quot;&gt;stuff&lt;/a&gt; is about the intent of the request and is apparently used to make decisions related to CSRF, CORS, etc.&lt;/p&gt;&lt;p&gt;&lt;code&gt;Upgrade-Insecure-Requests&lt;/code&gt; is telling the webserver to redirect me to &lt;code&gt;https&lt;/code&gt; if it can.&lt;/p&gt;&lt;p&gt;In general &lt;code&gt;sec-&lt;/code&gt; is a “&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Glossary/Forbidden_request_header&quot;&gt;forbidden request header&lt;/a&gt;“: only the browser is allowed to set these headers, not Javascript on the page. (Extensions can set them though.)&lt;/p&gt;&lt;p&gt;One other trivia I learned about: &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Trailer&quot;&gt;HTTP Trailers&lt;/a&gt;. This lets you set more headers at the &lt;em&gt;end&lt;/em&gt; of an HTTP request instead of the start. Say data you only know about after a long POST or something. These date all the way back to 1997 and HTTP/1.1 apparently but I’ve never encountered them. They don’t work with the simplest request format since there’s no way to identify the end of the HTTP body. Chunked encoding allows for them and I imagine later encodings have something similar.&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;Like&lt;/span&gt;&lt;/span&gt; &lt;span&gt;Loading…&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;</content:encoded>
</item>
<item>
<title>You should probably check on your smart appliances - Xe Iaso</title>
<link>https://xeiaso.net/notes/2026/check-your-smart-tv/</link>
<guid isPermaLink="false">GIPKoEIbp9lkWi6SEYhH2UMMGzI4NmOhU0UUOw==</guid>
<pubDate>Wed, 15 Jul 2026 00:39:33 +0000</pubDate>
<description>TL;DR: is your refrigerator running malware? If so, you better catch it!</description>
<content:encoded>&lt;h1&gt;You should probably check on your smart appliances&lt;/h1&gt;&lt;div&gt;
        &lt;div&gt;
            &lt;p&gt;
                Published on &lt;time&gt;2026-07-14&lt;/time&gt;, 1008 words, 4 minutes to read
            &lt;/p&gt;

            
                &lt;p&gt;TL;DR: is your refrigerator running malware? If so, you better catch it!&lt;/p&gt;
            
        &lt;/div&gt;
        &lt;div&gt;
            
        &lt;/div&gt;
    &lt;/div&gt;&lt;p&gt;The scraping problem is worse than anyone can imagine and thanks to my friends at Sourceware we have some real data to prove it.&lt;/p&gt;&lt;p&gt;I&amp;#39;ve been working more on Anubis&amp;#39; &lt;a href=&quot;https://github.com/TecharoHQ/reputationdb&quot;&gt;reputation database&lt;/a&gt; and I&amp;#39;ve run into a really weird discovery: 80-90% of the hits created by the &lt;a href=&quot;https://anubis.techaro.lol/docs/admin/honeypot/overview&quot;&gt;honeypot feature&lt;/a&gt; are from IP addresses that do not belong to any existing threat monitoring lists.&lt;/p&gt;&lt;p&gt;Here&amp;#39;s a breakdown of the honeypot hits &lt;a href=&quot;https://sourceware.org/&quot;&gt;Sourceware&lt;/a&gt; has gotten in the last few months:&lt;/p&gt;&lt;h2&gt;Assessment of ./data/manually-submitted/sourceware/202607141625.txt against ./var/reputationdb.mmdb&lt;/h2&gt;&lt;p&gt;In case this interests you, I have put the full tables in &lt;a href=&quot;https://xeiaso.net/notes/2026/check-your-smart-tv/full-table/&quot;&gt;Appendix A: Full tables for the reputation database input&lt;/a&gt;.&lt;/p&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Field&lt;/th&gt;&lt;th&gt;Value&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;lines read&lt;/td&gt;&lt;td&gt;2678193&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;skipped (non-IP):&lt;/td&gt;&lt;td&gt;0&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;skipped (dupe):&lt;/td&gt;&lt;td&gt;0&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;unique IPs:&lt;/td&gt;&lt;td&gt;2678193&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;flagged (in db):&lt;/td&gt;&lt;td&gt;286161 (10.7%)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;clean (not in):&lt;/td&gt;&lt;td&gt;2392032 (89.3%)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;h3&gt;Flags (of flagged addresses)&lt;/h3&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Flag&lt;/th&gt;&lt;th&gt;Unique IPs&lt;/th&gt;&lt;th&gt;Share&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;is_vpn&lt;/td&gt;&lt;td&gt;1264&lt;/td&gt;&lt;td&gt;0.4%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;is_datacenter&lt;/td&gt;&lt;td&gt;7918&lt;/td&gt;&lt;td&gt;2.8%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;is_crawler&lt;/td&gt;&lt;td&gt;46&lt;/td&gt;&lt;td&gt;0.0%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;is_proxy&lt;/td&gt;&lt;td&gt;2562&lt;/td&gt;&lt;td&gt;0.9%&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;h3&gt;Categories (6 distinct, of flagged addresses)&lt;/h3&gt;&lt;h3&gt;Providers (126 distinct, of flagged addresses)&lt;/h3&gt;&lt;div&gt;&lt;div&gt;&lt;img src=&quot;https://stickers.xeiaso.net/sticker/mara/hacker&quot; alt=&quot;Mara is hacker&quot; title=&quot;&quot;/&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;a href=&quot;https://xeiaso.net/characters#mara&quot;&gt;Mara&lt;/a&gt;&lt;/span&gt;&lt;div&gt;&lt;p&gt;Methodology note: &amp;quot;provider&amp;quot; here means one of two things:&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;The company or organization associated with the IP address.&lt;/li&gt;
&lt;li&gt;The place the list was gotten from.&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;For example, &lt;code&gt;scaleway&lt;/code&gt; is based off of &lt;a href=&quot;https://www.scaleway.com/en/docs/account/reference-content/scaleway-network-information/&quot;&gt;Scaleway&amp;#39;s publicly posted IP address ranges&lt;/a&gt;, &lt;code&gt;firehol-level1&lt;/code&gt; is based on a daily snapshot of &lt;a href=&quot;https://iplists.firehol.org/?ipset=firehol_level1&quot;&gt;FireHOL&amp;#39;s Level 1 IP list&lt;/a&gt;, and &lt;code&gt;fdo&lt;/code&gt; is based on data contributed by the administrators of &lt;a href=&quot;https://www.freedesktop.org/wiki/&quot;&gt;freedesktop.org&lt;/a&gt;.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Provider&lt;/th&gt;&lt;th&gt;Unique IPs&lt;/th&gt;&lt;th&gt;Share&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;netshield&lt;/td&gt;&lt;td&gt;237945&lt;/td&gt;&lt;td&gt;83.2%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;bitwire&lt;/td&gt;&lt;td&gt;96539&lt;/td&gt;&lt;td&gt;33.7%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;magicteamc&lt;/td&gt;&lt;td&gt;26475&lt;/td&gt;&lt;td&gt;9.3%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;ipinsights&lt;/td&gt;&lt;td&gt;17378&lt;/td&gt;&lt;td&gt;6.1%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;threathive&lt;/td&gt;&lt;td&gt;8422&lt;/td&gt;&lt;td&gt;2.9%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;netmountains&lt;/td&gt;&lt;td&gt;6673&lt;/td&gt;&lt;td&gt;2.3%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;multacom&lt;/td&gt;&lt;td&gt;2676&lt;/td&gt;&lt;td&gt;0.9%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;fyvri&lt;/td&gt;&lt;td&gt;2433&lt;/td&gt;&lt;td&gt;0.9%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;cbuijs&lt;/td&gt;&lt;td&gt;1916&lt;/td&gt;&lt;td&gt;0.7%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;x4bnet&lt;/td&gt;&lt;td&gt;1263&lt;/td&gt;&lt;td&gt;0.4%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;solispirit&lt;/td&gt;&lt;td&gt;1259&lt;/td&gt;&lt;td&gt;0.4%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;dailyproxy&lt;/td&gt;&lt;td&gt;1182&lt;/td&gt;&lt;td&gt;0.4%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;blackwall&lt;/td&gt;&lt;td&gt;1073&lt;/td&gt;&lt;td&gt;0.4%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;hproxy&lt;/td&gt;&lt;td&gt;1067&lt;/td&gt;&lt;td&gt;0.4%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;scaleway&lt;/td&gt;&lt;td&gt;922&lt;/td&gt;&lt;td&gt;0.3%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;fdo&lt;/td&gt;&lt;td&gt;755&lt;/td&gt;&lt;td&gt;0.3%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;datacamp&lt;/td&gt;&lt;td&gt;702&lt;/td&gt;&lt;td&gt;0.2%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;ebrasha&lt;/td&gt;&lt;td&gt;686&lt;/td&gt;&lt;td&gt;0.2%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;hideip&lt;/td&gt;&lt;td&gt;628&lt;/td&gt;&lt;td&gt;0.2%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;datacentres&lt;/td&gt;&lt;td&gt;480&lt;/td&gt;&lt;td&gt;0.2%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;komutan&lt;/td&gt;&lt;td&gt;463&lt;/td&gt;&lt;td&gt;0.2%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;aws&lt;/td&gt;&lt;td&gt;431&lt;/td&gt;&lt;td&gt;0.2%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;m247&lt;/td&gt;&lt;td&gt;360&lt;/td&gt;&lt;td&gt;0.1%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;firehol-level1&lt;/td&gt;&lt;td&gt;354&lt;/td&gt;&lt;td&gt;0.1%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;vpslab&lt;/td&gt;&lt;td&gt;331&lt;/td&gt;&lt;td&gt;0.1%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;alibaba-cloud&lt;/td&gt;&lt;td&gt;319&lt;/td&gt;&lt;td&gt;0.1%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;proxyscrape&lt;/td&gt;&lt;td&gt;272&lt;/td&gt;&lt;td&gt;0.1%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;ovhcloud&lt;/td&gt;&lt;td&gt;268&lt;/td&gt;&lt;td&gt;0.1%&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;(remainder snipped for brevity)&lt;/p&gt;&lt;h3&gt;Countries (229 distinct, of all addresses)&lt;/h3&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Country&lt;/th&gt;&lt;th&gt;Unique IPs&lt;/th&gt;&lt;th&gt;Flagged&lt;/th&gt;&lt;th&gt;Rate&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Brazil (BR)&lt;/td&gt;&lt;td&gt;270937&lt;/td&gt;&lt;td&gt;18282&lt;/td&gt;&lt;td&gt;6.7%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;India (IN)&lt;/td&gt;&lt;td&gt;185091&lt;/td&gt;&lt;td&gt;12478&lt;/td&gt;&lt;td&gt;6.7%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Saudi Arabia (SA)&lt;/td&gt;&lt;td&gt;120372&lt;/td&gt;&lt;td&gt;3574&lt;/td&gt;&lt;td&gt;3.0%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Mexico (MX)&lt;/td&gt;&lt;td&gt;95449&lt;/td&gt;&lt;td&gt;7053&lt;/td&gt;&lt;td&gt;7.4%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Türkiye (TR)&lt;/td&gt;&lt;td&gt;87258&lt;/td&gt;&lt;td&gt;5559&lt;/td&gt;&lt;td&gt;6.4%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Argentina (AR)&lt;/td&gt;&lt;td&gt;86463&lt;/td&gt;&lt;td&gt;9522&lt;/td&gt;&lt;td&gt;11.0%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Pakistan (PK)&lt;/td&gt;&lt;td&gt;85241&lt;/td&gt;&lt;td&gt;17083&lt;/td&gt;&lt;td&gt;20.0%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Vietnam (VN)&lt;/td&gt;&lt;td&gt;78967&lt;/td&gt;&lt;td&gt;8848&lt;/td&gt;&lt;td&gt;11.2%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Morocco (MA)&lt;/td&gt;&lt;td&gt;69201&lt;/td&gt;&lt;td&gt;1805&lt;/td&gt;&lt;td&gt;2.6%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Philippines (PH)&lt;/td&gt;&lt;td&gt;66128&lt;/td&gt;&lt;td&gt;7899&lt;/td&gt;&lt;td&gt;11.9%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Venezuela (VE)&lt;/td&gt;&lt;td&gt;64670&lt;/td&gt;&lt;td&gt;13780&lt;/td&gt;&lt;td&gt;21.3%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Iraq (IQ)&lt;/td&gt;&lt;td&gt;62047&lt;/td&gt;&lt;td&gt;13613&lt;/td&gt;&lt;td&gt;21.9%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Chile (CL)&lt;/td&gt;&lt;td&gt;60878&lt;/td&gt;&lt;td&gt;4522&lt;/td&gt;&lt;td&gt;7.4%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Colombia (CO)&lt;/td&gt;&lt;td&gt;59579&lt;/td&gt;&lt;td&gt;7048&lt;/td&gt;&lt;td&gt;11.8%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Bangladesh (BD)&lt;/td&gt;&lt;td&gt;59245&lt;/td&gt;&lt;td&gt;17735&lt;/td&gt;&lt;td&gt;29.9%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;France (FR)&lt;/td&gt;&lt;td&gt;49782&lt;/td&gt;&lt;td&gt;1339&lt;/td&gt;&lt;td&gt;2.7%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Tunisia (TN)&lt;/td&gt;&lt;td&gt;48535&lt;/td&gt;&lt;td&gt;5799&lt;/td&gt;&lt;td&gt;11.9%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Uruguay (UY)&lt;/td&gt;&lt;td&gt;45888&lt;/td&gt;&lt;td&gt;430&lt;/td&gt;&lt;td&gt;0.9%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;South Africa (ZA)&lt;/td&gt;&lt;td&gt;43919&lt;/td&gt;&lt;td&gt;7431&lt;/td&gt;&lt;td&gt;16.9%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;United States (US)&lt;/td&gt;&lt;td&gt;40828&lt;/td&gt;&lt;td&gt;3347&lt;/td&gt;&lt;td&gt;8.2%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Indonesia (ID)&lt;/td&gt;&lt;td&gt;38119&lt;/td&gt;&lt;td&gt;6122&lt;/td&gt;&lt;td&gt;16.1%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Canada (CA)&lt;/td&gt;&lt;td&gt;37342&lt;/td&gt;&lt;td&gt;2334&lt;/td&gt;&lt;td&gt;6.3%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Spain (ES)&lt;/td&gt;&lt;td&gt;36008&lt;/td&gt;&lt;td&gt;2944&lt;/td&gt;&lt;td&gt;8.2%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Algeria (DZ)&lt;/td&gt;&lt;td&gt;35112&lt;/td&gt;&lt;td&gt;537&lt;/td&gt;&lt;td&gt;1.5%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Ukraine (UA)&lt;/td&gt;&lt;td&gt;32261&lt;/td&gt;&lt;td&gt;8920&lt;/td&gt;&lt;td&gt;27.6%&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;This doesn&amp;#39;t list data from 204 additional countries. Given that the ISO 3166-1 standard comprises 249 countries (193 of which are UN members), it&amp;#39;s safe to say this is a &lt;em&gt;global&lt;/em&gt; problem.&lt;/p&gt;&lt;h3&gt;ASNs (21116 distinct, of all addresses)&lt;/h3&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;ASN&lt;/th&gt;&lt;th&gt;Unique IPs&lt;/th&gt;&lt;th&gt;Flagged&lt;/th&gt;&lt;th&gt;Rate&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;AS55836 Reliance Jio Infocomm Limited&lt;/td&gt;&lt;td&gt;57029&lt;/td&gt;&lt;td&gt;1749&lt;/td&gt;&lt;td&gt;3.1%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;AS45899 VNPT Corp&lt;/td&gt;&lt;td&gt;56910&lt;/td&gt;&lt;td&gt;6831&lt;/td&gt;&lt;td&gt;12.0%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;AS6057 Administracion Nacional de Telecomunicaciones&lt;/td&gt;&lt;td&gt;43694&lt;/td&gt;&lt;td&gt;339&lt;/td&gt;&lt;td&gt;0.8%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;AS25019 Saudi Telecom Company JSC&lt;/td&gt;&lt;td&gt;40800&lt;/td&gt;&lt;td&gt;679&lt;/td&gt;&lt;td&gt;1.7%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;AS24560 Bharti Airtel Ltd., Telemedia Services&lt;/td&gt;&lt;td&gt;35957&lt;/td&gt;&lt;td&gt;1620&lt;/td&gt;&lt;td&gt;4.5%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;AS36903 Office National des Postes et Telecommunications ONPT (Maroc Telecom) / IAM&lt;/td&gt;&lt;td&gt;35562&lt;/td&gt;&lt;td&gt;668&lt;/td&gt;&lt;td&gt;1.9%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;AS36947 Telecom Algeria&lt;/td&gt;&lt;td&gt;33172&lt;/td&gt;&lt;td&gt;386&lt;/td&gt;&lt;td&gt;1.2%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;AS9121 Turk Telekom&lt;/td&gt;&lt;td&gt;32742&lt;/td&gt;&lt;td&gt;1465&lt;/td&gt;&lt;td&gt;4.5%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;AS8151 UNINET&lt;/td&gt;&lt;td&gt;32012&lt;/td&gt;&lt;td&gt;856&lt;/td&gt;&lt;td&gt;2.7%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;AS14593 Space Exploration Technologies Corporation&lt;/td&gt;&lt;td&gt;31569&lt;/td&gt;&lt;td&gt;4597&lt;/td&gt;&lt;td&gt;14.6%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;AS9299 Philippine Long Distance Telephone Company&lt;/td&gt;&lt;td&gt;27573&lt;/td&gt;&lt;td&gt;1626&lt;/td&gt;&lt;td&gt;5.9%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;AS39891 Saudi Telecom Company JSC&lt;/td&gt;&lt;td&gt;25904&lt;/td&gt;&lt;td&gt;794&lt;/td&gt;&lt;td&gt;3.1%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;AS35819 Etihad Etisalat, a joint stock company&lt;/td&gt;&lt;td&gt;24493&lt;/td&gt;&lt;td&gt;978&lt;/td&gt;&lt;td&gt;4.0%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;AS28573 Claro NXT Telecomunicacoes Ltda&lt;/td&gt;&lt;td&gt;23903&lt;/td&gt;&lt;td&gt;841&lt;/td&gt;&lt;td&gt;3.5%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;AS8193 Uzbektelekom Joint Stock Company&lt;/td&gt;&lt;td&gt;22611&lt;/td&gt;&lt;td&gt;3191&lt;/td&gt;&lt;td&gt;14.1%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;AS8452 IDDQD-AS&lt;/td&gt;&lt;td&gt;22369&lt;/td&gt;&lt;td&gt;364&lt;/td&gt;&lt;td&gt;1.6%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;AS43766 Mobile Telecommunication Company Saudi Arabia Joint-Stock company&lt;/td&gt;&lt;td&gt;22038&lt;/td&gt;&lt;td&gt;968&lt;/td&gt;&lt;td&gt;4.4%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;AS9541 Cyber Internet Services (Pvt) Ltd.&lt;/td&gt;&lt;td&gt;21386&lt;/td&gt;&lt;td&gt;3696&lt;/td&gt;&lt;td&gt;17.3%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;AS37705 TOPNET&lt;/td&gt;&lt;td&gt;20024&lt;/td&gt;&lt;td&gt;222&lt;/td&gt;&lt;td&gt;1.1%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;AS11664 Techtel LMDS Comunicaciones Interactivas S.A.&lt;/td&gt;&lt;td&gt;18021&lt;/td&gt;&lt;td&gt;883&lt;/td&gt;&lt;td&gt;4.9%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;AS17072 TOTAL PLAY TELECOMUNICACIONES, S.A.P.I. DE C.V.&lt;/td&gt;&lt;td&gt;18021&lt;/td&gt;&lt;td&gt;1181&lt;/td&gt;&lt;td&gt;6.6%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;AS22927 Telefonica de Argentina&lt;/td&gt;&lt;td&gt;17672&lt;/td&gt;&lt;td&gt;291&lt;/td&gt;&lt;td&gt;1.6%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;AS13999 Mega Cable, S.A. de C.V.&lt;/td&gt;&lt;td&gt;17410&lt;/td&gt;&lt;td&gt;692&lt;/td&gt;&lt;td&gt;4.0%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;AS36925 MEDITELECOM&lt;/td&gt;&lt;td&gt;17259&lt;/td&gt;&lt;td&gt;383&lt;/td&gt;&lt;td&gt;2.2%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;AS47331 Turk Telekom&lt;/td&gt;&lt;td&gt;17211&lt;/td&gt;&lt;td&gt;26&lt;/td&gt;&lt;td&gt;0.2%&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;There are 18069 more ASNs not listed.&lt;/p&gt;&lt;h2&gt;How Anubis&amp;#39; honeypot works&lt;/h2&gt;&lt;p&gt;In order to collect data on how widespread the scraper problem is, I added a &lt;a href=&quot;https://anubis.techaro.lol/docs/admin/honeypot/overview&quot;&gt;honeypot feature&lt;/a&gt; to Anubis. On every challenge page it adds semantically invalid HTML akin to the following:&lt;/p&gt;&lt;p&gt;Visiting that page gets you cheap to generate vacuous anti-content that has two links to other pages. This is intended to get badly written scrapers caught in the honeypot so they scrape that instead of the protected website. I made it on a whim but thought it would be great for collecting data on how widespread this problem actually is.&lt;/p&gt;&lt;h2&gt;This is a global problem&lt;/h2&gt;&lt;p&gt;Based on the data I&amp;#39;ve seen, this is a global problem. If I had to guess where most of this traffic is coming from, it&amp;#39;s from &lt;a href=&quot;https://thehackernews.com/2026/06/free-apps-are-quietly-turning-smart-tvs.html&quot;&gt;compromised smart appliances&lt;/a&gt; contributing traffic to proxy networks. I don&amp;#39;t think there&amp;#39;s any way to make a real impact on this problem without concerted simultaneous global action.&lt;/p&gt;&lt;p&gt;TL;DR: the scraping problem is actually widespread enough that web application firewalls like Anubis make sense.&lt;/p&gt;&lt;hr/&gt;&lt;p&gt;Facts and circumstances may have changed since publication. Please contact me before jumping to conclusions if something seems wrong or unclear.&lt;/p&gt;&lt;p&gt;Tags: &lt;/p&gt;</content:encoded>
</item>
<item>
<title>Too many words about DIDs</title>
<link>https://steveklabnik.com/writing/too-many-words-about-dids/</link>
<guid isPermaLink="false">_P59h9uR7GSdCLSi4K_6mwHWGVpgiOG95jan0A==</guid>
<pubDate>Tue, 14 Jul 2026 18:39:07 +0000</pubDate>
<description>Blog post: Too many words about DIDs by Steve Klabnik</description>
<content:encoded>&lt;h1&gt;Too many words about DIDs&lt;/h1&gt;&lt;time&gt; Jul 14, 2026 &lt;/time&gt;&lt;div&gt; &lt;a href=&quot;https://steveklabnik.com/topics/atproto/&quot;&gt; ATProto &amp;amp; Bluesky &lt;/a&gt; &lt;/div&gt;&lt;p&gt;Your “Bluesky account” is not &lt;em&gt;just&lt;/em&gt; a Bluesky account: it is an account that
can be used with a variety of other applications. This post is going to be an
exploration of part of what that means from a technical perspective, so if you’re
not a software developer, this post isn’t for you. But what I’m going to explain
is the technical mechanism for how your account works separate from Bluesky, and
in fact, separate from any particular app.&lt;/p&gt;&lt;p&gt;Let’s talk about identity: who are you, anyway? Users of a system need some sort
of way to describe who they are to use it. If you want to log in, you need to
present who you are. If you want to make a post, well, we need to know who the
author of that post is. For atproto, the protocol that underlies Bluesky and
other apps in the ATmosphere, they use the “Decentralized Identity” standard,
also known as DID. The &lt;a href=&quot;https://www.w3.org/TR/did-1.0/&quot;&gt;W3C standardized DIDs in
2022&lt;/a&gt;. As you might guess from the name, DIDs
are, an identifier that you can use as the basis of identity for building
applications. And the idea is that these identifiers are decentralized. However,
a lot of people have a lot of feelings about that specific word, and often
accuse atproto of not being properly decentralized. We’re going to go over the
details so you can understand how this works, and you can decide for yourself if
this approach suits you or not.&lt;/p&gt;&lt;h2&gt;DIDs and DID Documents&lt;/h2&gt;&lt;p&gt;Here is my DID, we’ll use this as an example: &lt;code&gt;did:plc:3danwc67lo7obz2fmdg6jxcr&lt;/code&gt;&lt;/p&gt;&lt;p&gt;There are three parts, separated by colons: The scheme (&lt;code&gt;did&lt;/code&gt;), the method (&lt;code&gt;plc&lt;/code&gt;),
and the DID method-specific identifier (&lt;code&gt;3danwc67lo7obz2fmdg6jxcr&lt;/code&gt;).&lt;/p&gt;&lt;p&gt;To use a DID, such as &lt;code&gt;did:plc:3danwc67lo7obz2fmdg6jxcr&lt;/code&gt;, you resolve it into a
&lt;a href=&quot;https://www.w3.org/TR/did-1.0/#dfn-did-documents&quot;&gt;DID Document&lt;/a&gt;&lt;/p&gt;&lt;blockquote&gt;
&lt;p&gt;A set of data describing the DID subject, including mechanisms, such as
cryptographic public keys, that the DID subject or a DID delegate can use to
authenticate itself and prove its association with the DID.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;That document contains &lt;a href=&quot;https://www.w3.org/TR/did-1.0/#core-properties&quot;&gt;various
properties&lt;/a&gt; that describe the
identity. Here’s my DID Document, at the time of writing:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;{
  &amp;quot;@context&amp;quot;: [
    &amp;quot;https://www.w3.org/ns/did/v1&amp;quot;,
    &amp;quot;https://w3id.org/security/multikey/v1&amp;quot;,
    &amp;quot;https://w3id.org/security/suites/secp256k1-2019/v1&amp;quot;
  ],
  &amp;quot;id&amp;quot;: &amp;quot;did:plc:3danwc67lo7obz2fmdg6jxcr&amp;quot;,
  &amp;quot;alsoKnownAs&amp;quot;: [&amp;quot;at://steveklabnik.com&amp;quot;],
  &amp;quot;verificationMethod&amp;quot;: [
    {
      &amp;quot;id&amp;quot;: &amp;quot;did:plc:3danwc67lo7obz2fmdg6jxcr#atproto&amp;quot;,
      &amp;quot;type&amp;quot;: &amp;quot;Multikey&amp;quot;,
      &amp;quot;controller&amp;quot;: &amp;quot;did:plc:3danwc67lo7obz2fmdg6jxcr&amp;quot;,
      &amp;quot;publicKeyMultibase&amp;quot;: &amp;quot;zQ3shfNec2kPEb8cL77gSRMbCwbWE27p9nxKkcc4E82xtW8RJ&amp;quot;
    }
  ],
  &amp;quot;service&amp;quot;: [
    {
      &amp;quot;id&amp;quot;: &amp;quot;#atproto_pds&amp;quot;,
      &amp;quot;type&amp;quot;: &amp;quot;AtprotoPersonalDataServer&amp;quot;,
      &amp;quot;serviceEndpoint&amp;quot;: &amp;quot;https://morel.us-east.host.bsky.network&amp;quot;
    }
  ]
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This document gives you everything you need to know to determine who I am, that is,
given an arbitrary post that claims it’s written by me, this document describes how
you’d verify that claim.&lt;/p&gt;&lt;p&gt;We’ll get into how to do that that in a moment, but first, how do you resolve
that DID into that DID document? Well, it’s pretty easy: each method is a
standard that describes how you do that. So when you see &lt;code&gt;did:plc&lt;/code&gt;, that means
we use the PLC standard, which we’ll be going over in a moment. Another method
supported by Bluesky is &lt;code&gt;did:web&lt;/code&gt;. In that case, you wouldn’t use the PLC
standard, you’d use the Web one.&lt;/p&gt;&lt;p&gt;This is the sense in which DIDs are decentralized: when you present your
identity, you get to decide what method validates that that is a real identity.
There’s no centralized authority that determines which DID types are valid. Now,
of course, that doesn’t mean that every application supports every DID method,
because while this specification is &lt;em&gt;very&lt;/em&gt; generic, you’re still going to have
to write some code to implement that particular method. I could say “Hey I’m
&lt;code&gt;did:foo:1243&lt;/code&gt;” and unless your app supports the &lt;code&gt;foo&lt;/code&gt; method, it’s not gonna
inherently just know what to do. So that is one important caveat.&lt;/p&gt;&lt;h2&gt;&lt;code&gt;did:web&lt;/code&gt;&lt;/h2&gt;&lt;p&gt;Let’s explain this resolution process for the &lt;code&gt;web&lt;/code&gt; method. While supported by
Bluesky, a very small number of users actually use &lt;code&gt;did:web&lt;/code&gt;, but it’s a simpler
method and so I think it’s illustrative to go over first. I’ll be using &lt;a href=&quot;https://bsky.app/profile/web.lizthegrey.com&quot;&gt;Liz
Fong-Jones&lt;/a&gt; &lt;code&gt;did:web&lt;/code&gt; account as an
example here. Her identity for that account is &lt;code&gt;did:web:lizthegrey.com&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;So how do we resolve this DID into a DID Document? We take the method-specific
identifier, which in this case is &lt;code&gt;lizthegrey.com&lt;/code&gt;, and put it into this URL template:&lt;/p&gt;&lt;p&gt;&lt;code&gt;https://&amp;lt;id&amp;gt;/.well-known/did.json&lt;/code&gt;&lt;/p&gt;&lt;p&gt;You can then go fetch this URL to resolve it into the DID Document, which at the
time of writing, looks like this:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;{
  &amp;quot;@context&amp;quot;: [
    &amp;quot;https://www.w3.org/ns/did/v1&amp;quot;,
    &amp;quot;https://w3id.org/security/multikey/v1&amp;quot;,
    &amp;quot;https://w3id.org/security/suites/secp256k1-2019/v1&amp;quot;
  ],
  &amp;quot;id&amp;quot;: &amp;quot;did:web:lizthegrey.com&amp;quot;,
  &amp;quot;alsoKnownAs&amp;quot;: [
    &amp;quot;at://web.lizthegrey.com&amp;quot;,
    &amp;quot;did:plc:i4tfenpfog244rxry5uz4vtk&amp;quot;
  ],
  &amp;quot;verificationMethod&amp;quot;: [
    {
      &amp;quot;id&amp;quot;: &amp;quot;did:web:lizthegrey.com#atproto&amp;quot;,
      &amp;quot;type&amp;quot;: &amp;quot;Multikey&amp;quot;,
      &amp;quot;controller&amp;quot;: &amp;quot;did:web:lizthegrey.com&amp;quot;,
      &amp;quot;publicKeyMultibase&amp;quot;: &amp;quot;zQ3shnEqBNR5cuTePW8FyvnrRQaFf6Y7sCi5NBmDpteVXFjb6&amp;quot;
    }
  ],
  &amp;quot;service&amp;quot;: [
    {
      &amp;quot;id&amp;quot;: &amp;quot;#atproto_pds&amp;quot;,
      &amp;quot;type&amp;quot;: &amp;quot;AtprotoPersonalDataServer&amp;quot;,
      &amp;quot;serviceEndpoint&amp;quot;: &amp;quot;https://pds.lizthegrey.com&amp;quot;
    }
  ]
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is very simple! So why might we not want to use &lt;code&gt;did:web&lt;/code&gt;? Why bother with
any other system? Well, this relies on the DNS system. One could make the
argument that ultimately, this is still centralized in some form. If Liz’s
domain registrar were to take away her domain, she would also lose control of
this DID. In a more generic sense, if Liz decides she wants to not use that
domain anymore, she will lose control of that identity to whoever does. That
could be through non-malicious means, like letting it expire and someone else
purchases it, or through malicious ones, like a hack which would compromise her
registrar account and take the domain over.&lt;/p&gt;&lt;p&gt;Also, you need to have a web server running on that domain with infinite uptime;
if the server goes down, so does your ability to get the document.&lt;/p&gt;&lt;p&gt;When this DID document changes, there’s no mechanism for clients to know that
it’s changed, which means applications may use one that’s out of date, or that
there is lag between updating the document and updating the application built on
it, which may cause temporary problems until the latest document is fetched.&lt;/p&gt;&lt;h2&gt;&lt;code&gt;did:plc&lt;/code&gt;&lt;/h2&gt;&lt;p&gt;All of these drawbacks led Bluesky to develop their own DID method, which
attempts to fix these problems and others. This method is called
&lt;a href=&quot;https://web.plc.directory/spec/v0.1/did-plc&quot;&gt;&lt;code&gt;did:plc&lt;/code&gt;&lt;/a&gt;. To resolve a &lt;code&gt;did:plc&lt;/code&gt;,
you take the entire DID, and put it in this template:&lt;/p&gt;&lt;p&gt;&lt;code&gt;https://plc.directory/&amp;lt;did&amp;gt;&lt;/code&gt;&lt;/p&gt;&lt;p&gt;You can then fetch that URL and get the DID document.&lt;/p&gt;&lt;p&gt;So… what’s the difference? Well, in this case, both nothing and something. In
a very literal sense, both are resolved in the same way: you fetch a URL.
However, the details matter. There is already two ways in which this is different
than DID:Web:&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;Your DID is no longer tied to a specific domain name. I can let
&lt;code&gt;steveklabnik.com&lt;/code&gt; expire and move to &lt;code&gt;steve.klabnik.com&lt;/code&gt; and my &lt;code&gt;did:plc&lt;/code&gt;
stays the same.&lt;/li&gt;
&lt;li&gt;While a web server still needs to be running, that’s the job of plc.directory, not
my own job. This is operationally much simpler.&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;I’ve presented the above as pros, but there are also cons. Before, I had to
trust the DNS system and domain registrars, now I have to trust plc.directory.
All of the same caveats apply in that sense, I have to trust that they don’t
take my ID away from me, or that it doesn’t get stolen, etc. However, there are
also some important details that mitigate this, which we’ll get to. But for some
people, neither trusting DNS nor trusting plc.directory is acceptable, and there
are other DID methods that use, for example, a blockchain to resolve the name.
Bluesky does not support using any of those DID methods, so for this
application, it’s not really relevant, but it’s important to know that they
exist.&lt;/p&gt;&lt;p&gt;Why do it this way? Well, the simplest way to put it is this: setting up a
&lt;code&gt;did:web&lt;/code&gt; involves a lot of “nerd stuff.” You have to register a domain, and
that’s also an ongoing monetary cost. You have to know how to set up a web
server, and author some JSON to put on that server. You have to keep it running.
You have to know how to store your private keys, and keep them safe. It’s a
non-starter compared to “sign up for this web app.” And Bluesky’s goals involves
making this platform accessible to non-nerds. By having plc.directory manage
all of this, we eliminate all of those steps.&lt;/p&gt;&lt;p&gt;While drafting this post, I have also been made aware of
&lt;a href=&quot;https://identity.foundation/didwebvh/v1.0/&quot;&gt;&lt;code&gt;did:webvh&lt;/code&gt;&lt;/a&gt;, which expands on &lt;code&gt;did:web&lt;/code&gt; and attempts to rectify
some of its shortcomings. I have not read the spec yet, but it has reached 1.0,
so it is probably worth checking out. I wanted to get this post shipped last
week, and didn’t want to delay it further by adding another section, but if
I were writing this post in the future, I’d probably want to talk about it as
well, so just a little heads-up there.&lt;/p&gt;&lt;h2&gt;Does Bluesky own your identity?&lt;/h2&gt;&lt;p&gt;But it does also mean that, in some sense, Bluesky still owns your identity.
They’ve generated a keypair for you, and the have access to the secret key.
That’s unacceptable for some people. So how do you fix that?&lt;/p&gt;&lt;p&gt;Well, &lt;code&gt;did:plc&lt;/code&gt; has some additional features that &lt;code&gt;did:web&lt;/code&gt; does not. For
example, &lt;code&gt;did:plc&lt;/code&gt; will allow you to register additional keypairs with your ID
and use them to rotate your signing keys. This allows you to remove the Bluesky
generated keys and insert your own.&lt;/p&gt;&lt;p&gt;While that is true, it’s also the case that your PDS needs to use your keys to
sign your posts. As such, most people are likely to store their keys in their PDS,
and so if you are using a Bluesky managed PDS, well, you’ve uploaded your keys to
their infrastructure, and that’s probably not acceptable if you’re trying to keep
your identity away from Bluesky. Of course, the solution there is to run your own
PDS and then rotate your keys. At that point, your key is living on infrastructure
you own, and Bluesky has no say over it any more.&lt;/p&gt;&lt;p&gt;I think that this possibility is an important design property, and allows
motivated users to meaningfully own their identity. A criticism of this boils
down to “well, most users won’t do that,” and while that’s true, I also think
that’s okay for most people, and that having the choice is more important than
forcing every user to deal with their own key management.&lt;/p&gt;&lt;h2&gt;In summary&lt;/h2&gt;&lt;p&gt;This is kind of an abrupt end to this post, but I just wanted to get some things
down ‘on paper’ as it were. I hope you’ve learned a bit about identity and how
it works with atproto.&lt;/p&gt;&lt;hr/&gt;&lt;p&gt;Here’s my post about this post on BlueSky:&lt;/p&gt;</content:encoded>
</item>
<item>
<title>Just Let Me Write Digits | Blog | Guillaume Endignoux</title>
<link>https://gendignoux.com/blog/2026/07/13/input-digits.html</link>
<guid isPermaLink="false">0AocwRZOHXpuA-RrFUBofugv_rMM9i0KP3z48A==</guid>
<pubDate>Mon, 13 Jul 2026 19:17:18 +0000</pubDate>
<description>Digital identities and what they mean for the web have become a hot topic of discussion in the last few years.They have also brought many controversies: age ...</description>
<content:encoded>&lt;p&gt;Digital identities and what they mean for the web have become a hot topic of discussion in the last few years.
They have also brought many controversies: age verification laws and &lt;a href=&quot;https://www.eff.org/deeplinks/2025/12/age-verification-coming-internet-we-built-you-resource-hub-fight-back&quot;&gt;what they mean for online anonymity&lt;/a&gt;, Wikipedia potentially having to &lt;a href=&quot;https://wikimediafoundation.org/news/2025/09/12/wikimedia-foundation-challenges-uk-online-safety-act-regulations/&quot;&gt;verify the identify of its users&lt;/a&gt; in the UK, the reliance on official iOS and Android operating systems as mandatory form factors for &lt;a href=&quot;https://github.com/eu-digital-identity-wallet/eudi-app-android-wallet-ui/issues/287&quot;&gt;digital identity wallets&lt;/a&gt;, not to mention what these inter-dependencies mean when your US digital accounts are &lt;a href=&quot;https://www.theguardian.com/law/2026/feb/18/international-criminal-court-icc-judges-trump-sanctions&quot;&gt;cancelled for being a judge at the International Criminal Court&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;My story is about the Swiss governmental login system &lt;a href=&quot;https://www.agov.admin.ch/en&quot;&gt;AGOV&lt;/a&gt;.
Deployed since 2024, it now counts &lt;a href=&quot;https://www.agov.admin.ch/en/agov-statse-2026-04&quot;&gt;1.6 million accounts&lt;/a&gt; and it is increasingly becoming the login mechanism to benefit from unemployment insurance, file taxes (a mandatory activity!), and many other interactions.
In the canton of Zurich, this is the only entry point to &lt;a href=&quot;https://www.zh.ch/de/migration-integration/einbuergerung/ordentliche-einbuergerung.html&quot;&gt;apply for citizenship&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;So I eventually had to create an AGOV account.
Unfortunately, registering an account proved to be a real challenge, until I got the bottom of a weird accessibility bug.
Inspired by &lt;a href=&quot;https://aartaka.me/select-text.html&quot;&gt;&lt;em&gt;Just Let Me Select Text&lt;/em&gt;&lt;/a&gt;, it’s my turn to present you &lt;em&gt;Just Let Me Write Digits&lt;/em&gt;.&lt;/p&gt;&lt;hr/&gt;&lt;ul&gt;
  &lt;li&gt;AGOV registration&lt;/li&gt;
  &lt;li&gt;Contacting support?&lt;/li&gt;
  &lt;li&gt;Asking around&lt;/li&gt;
  &lt;li&gt;Investigating the web page&lt;/li&gt;
  &lt;li&gt;Conclusion&lt;/li&gt;
  &lt;li&gt;Post-mortem&lt;/li&gt;
&lt;/ul&gt;&lt;h2&gt;AGOV registration&lt;/h2&gt;&lt;p&gt;The first step of the AGOV &lt;a href=&quot;https://agov.ch/me&quot;&gt;registration process&lt;/a&gt; is to provide your email address and enter a verification code to validate the address.
My problem was that I couldn’t even enter this code!&lt;/p&gt;&lt;p&gt;To set the context, the form to enter the verification code is split into 6 boxes (one per digit).
The first weird thing was that every time I typed a digit, the focus wouldn’t automatically jump to the next box.
Instead, the current box would accumulate more digits and highlight them in red, indicating an error.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://gendignoux.com/blog/images/input-digits/screenshot-digits-fr.zEFxM4-uwfRu.png&quot;&gt;&lt;img src=&quot;https://gendignoux.com/blog/images/input-digits/screenshot-digits-fr.h6G-VpBKuN5N.webp&quot; alt=&quot;Screenshot of the verification code form with 2 digits in the first box&quot; title=&quot;&quot;/&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;At first, I thought that this was just a minor UI bug, and that I could manually jump to the next box after each digit.
However, this didn’t work and the page would always display a “field required” error at the end, even when I carefully filled one digit per box.
Strange.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://gendignoux.com/blog/images/input-digits/screenshot-field-required-fr.fgJ6FXO-4IJX.png&quot;&gt;&lt;img src=&quot;https://gendignoux.com/blog/images/input-digits/screenshot-field-required-fr.LesOci-eJr2F.webp&quot; alt=&quot;Screenshot of the verification code form with 6 digits but a &amp;quot;Field required&amp;quot; error&quot; title=&quot;&quot;/&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;I tried looking at the DOM in the developer tools to see if there was a form value I could manually adjust, but nothing obvious stood out.
No error message was logged either in the web console.
Using a different email address, or even a fake one&lt;sup&gt;1&lt;/sup&gt; like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test@example.com&lt;/code&gt; didn’t make any difference.&lt;/p&gt;&lt;p&gt;Given that I was browsing the web with Firefox on Linux, I thought that maybe this combination was unsupported or failing a CAPTCHA.
This would be unlikely – I barely encounter any website broken specifically on Firefox or Linux these days – but not impossible.
Indeed, the registration page connects to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;eu-api.friendlycaptcha.eu&lt;/code&gt; just before the verification code form appears.
And the &lt;a href=&quot;https://help.agov.ch/index.php?c=regrequiremets&amp;amp;l=en&quot;&gt;official AGOV documentation&lt;/a&gt; only mentions Windows and macOS as supported operating systems, even though a different &lt;a href=&quot;https://help.agov.ch/index.php?c=seckeys&amp;amp;l=en&quot;&gt;page about security keys&lt;/a&gt; states that Linux and Firefox are supported.&lt;/p&gt;&lt;p&gt;To rule this out, I ended up trying 6 combinations of browsers and operating systems: Firefox, Chromium and Chrome on Linux; Firefox, Safari and Chrome on macOS.
All of them showed the same error!&lt;/p&gt;&lt;h2&gt;Contacting support?&lt;/h2&gt;&lt;p&gt;At this point I thought it’d make sense to contact the &lt;a href=&quot;https://help.agov.ch/index.php?c=contact&amp;amp;l=en&quot;&gt;official support&lt;/a&gt;.
The AGOV website has &lt;a href=&quot;https://help.agov.ch/index.php?c=agov&amp;amp;l=en&quot;&gt;many help sections&lt;/a&gt;, but unfortunately, this didn’t lead me very far because the only way to &lt;a href=&quot;https://help.agov.ch/index.php?c=contact1&amp;amp;l=en&quot;&gt;contact support&lt;/a&gt; is via a web form… that starts by sending a verification code to your email!
The exact same form I couldn’t pass.&lt;/p&gt;&lt;p&gt;And yes, the FAQ &lt;a href=&quot;https://help.agov.ch/?c=agovfaq&amp;amp;q=2.3&amp;amp;l=en&quot;&gt;explicitly mentions&lt;/a&gt; that this form is the only way to contact support (emphasis mine).&lt;/p&gt;&lt;blockquote&gt;
  &lt;p&gt;Support for participating authorities is provided by second-level support during office hours &lt;strong&gt;exclusively through the creation of tickets online&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;The &lt;a href=&quot;https://help.agov.ch/index.php?c=impressum&amp;amp;l=en&quot;&gt;impressum&lt;/a&gt; also lists the physical postal address of the Swiss Federal Chancellery, who are likely not the ones directly developing the website anyway.
There are no other contact details: no email, no phone number.
Alternatively, one can in principle &lt;a href=&quot;https://help.agov.ch/index.php?c=contactrems&amp;amp;l=en&quot;&gt;write a comment&lt;/a&gt; to “share feedback on AGOV: praise, criticism, requests”, but that again requires an AGOV or equivalent account to log in.
A true chicken and egg problem!&lt;/p&gt;&lt;p&gt;I also looked at the online reviews of the &lt;a href=&quot;https://play.google.com/store/apps/details?id=ch.agov.accessapp&amp;amp;hl=en&quot;&gt;AGOV Access Android app&lt;/a&gt;, which is currently rated 1.4 stars – by far the worst of any app I’m using.
Unfortunately, nobody mentioned an issue similar to mine on these Google Play store comments.
Interestingly, there were many comments about support for &lt;a href=&quot;https://grapheneos.org/&quot;&gt;GrapheneOS&lt;/a&gt; (a private and secure mobile OS derived from Android) and the official FAQ now even has an &lt;a href=&quot;https://help.agov.ch/?c=agovfaq&amp;amp;q=2.9&amp;amp;l=en&quot;&gt;entry about it&lt;/a&gt; (emphasis mine)!&lt;/p&gt;&lt;blockquote&gt;
  &lt;p&gt;The AGOV access App does not work on GrapheneOS because the hardening framework, which protects the AGOV access App against unauthorized manipulation, is not compatible with GrapheneOS. &lt;strong&gt;The Federal Chancellery has tasked the supplier with ensuring compatibility.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;It sounds promising, but for now you can only register/login with a second-factor device that’s either a smartphone with vanilla iOS or Android, or a security key (good that it’s an option!).
Of course, after you pass email verification.&lt;/p&gt;&lt;h2&gt;Asking around&lt;/h2&gt;&lt;p&gt;I wasn’t in a rush to create my AGOV account yet, so I casually mentioned this issue to some friends, but nobody seemed to have encountered this problem.&lt;/p&gt;&lt;p&gt;I also thought to ask some colleagues and tried the registration page on my work laptop to make a screenshot to show them evidence.
Surprisingly, the form worked this time!
The autofill automatically jumped to the next box, and it actually detected that I entered an incorrect code rather than no code at all!&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://gendignoux.com/blog/images/input-digits/screenshot-incorrect-code-us.ki-RkPvXCl8b.png&quot;&gt;&lt;img src=&quot;https://gendignoux.com/blog/images/input-digits/screenshot-incorrect-code-us.IoABW4eiSE6u.webp&quot; alt=&quot;Screenshot of the verification code form with 6 digits and a &amp;quot;Code entered is incorrect&amp;quot; error&quot; title=&quot;&quot;/&gt;&lt;/a&gt;
This time the error was “Code entered is incorrect”.&lt;/p&gt;&lt;p&gt;But I obviously don’t want to rely on an employer-issued computer for long-term interactions with the government.
Even if I managed to register via my work laptop, there was always a risk that I wouldn’t be able to login with my personal laptop.&lt;/p&gt;&lt;p&gt;Unfortunately, none of my colleagues had encountered my error…
I also tried on a friend’s computer, with the same three browsers on macOS, and all of them accepted a verification code!&lt;/p&gt;&lt;p&gt;Was I really that unlucky to own the only two computers in the world for which this website breaks??&lt;/p&gt;&lt;h2&gt;Investigating the web page&lt;/h2&gt;&lt;p&gt;At this point I was out of simple ideas, so I thought to inspect the web page.
It surprisingly loads only a few resources, but the main JavaScript file was 2.4MB, expanding to more than 100K lines of semi-obfuscated/minified code.
This looked quite daunting as some parts looked very obfuscated at first glance.&lt;/p&gt;&lt;p&gt;So I decided to wait a bit more in case someone would have an idea.&lt;/p&gt;&lt;blockquote&gt;
  &lt;p&gt;&lt;a href=&quot;https://help.agov.ch/?c=agovfaq&amp;amp;q=4.10&amp;amp;l=en&quot;&gt;According to the FAQ&lt;/a&gt;, the source code of the AGOV system will be published at the end of 2026 to satisfy legal requirements.&lt;/p&gt;

  &lt;blockquote&gt;
    &lt;p&gt;Article 9 &lt;a href=&quot;https://www.fedlex.admin.ch/eli/fga/2023/787/de&quot;&gt;EMBAG&lt;/a&gt; stipulates in paragraph 1:&lt;/p&gt;

    &lt;p&gt;“The federal authorities subject to this law shall disclose the source code of software that they develop or have developed for the fulfilment of their tasks, unless third party rights or security related reasons prevent or limit such disclosure.”&lt;/p&gt;

    &lt;p&gt;In December 2026, the source code of AGOV will be published in accordance with this requirement. This is the AGOV version that represents the achievement of the AGOV project goal, namely AGOV including AGOV e-ID Verifier. Later versions will be described in the release notes &lt;a href=&quot;https://www.agov.ch/rn&quot;&gt;www.agov.ch/rn&lt;/a&gt;, prepared for publication after release and published.&lt;/p&gt;
  &lt;/blockquote&gt;

  &lt;p&gt;Having the source code upfront and a place to report bugs directly to developers would have helped my investigation, but unfortunately for me, AGOV is already the mandatory login system for some services before its source code has been published!&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;I eventually came back to it, and fortunately searching for “field required” gave some hints into the logic.&lt;/p&gt;&lt;p&gt;Continuing the search with “required”, I found this email verification function that sets the status code to “REQUIRED”.&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre&gt;&lt;code&gt;onEmailVerification() {
  var _ = this;
  return (0, ve.A)(function* () {
    ((_.showResendSuccessfulBanner = !1),
      _.verificationCode
        ? _.verificationCode.length === _.verificationCodeLength
          ? (_.statusCode.set(&amp;quot;&amp;quot;),
            (_.isLoading = !0),
            yield (0, Qt.s)(
              _.registrationService.emailVerification(_.verificationCode, _.userEmail),
            )
              .then((S) =&amp;gt; {
                &amp;quot;VALID&amp;quot; === S.status
                  ? _.onEmailVerificationSuccess()
                  : ((_.showCodeValidation = !0), _.statusCode.set(S.status ?? &amp;quot;WRONG&amp;quot;));
              })
              .catch(() =&amp;gt; {
                (_.statusCode.set(&amp;quot;WRONG&amp;quot;), (_.showCodeValidation = !0));
              }),
            (_.isLoading = !1))
          : (_.statusCode.set(&amp;quot;WRONG&amp;quot;), (_.showCodeValidation = !0))
        : (_.statusCode.set(&amp;quot;REQUIRED&amp;quot;), (_.showCodeValidation = !0)),
      _.changeDetectorRef.detectChanges());
  })();
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;Eventually, I found the following function, which calls the email verification callback.&lt;/p&gt;&lt;p&gt;At this point the bug became clear to me!
Have you found it?&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;pre&gt;&lt;code&gt;verificationCodeEntered(_, S) {
    if (&amp;quot;Shift&amp;quot; === this.lastKeyPressed) return;
    switch (S.key) {
      case &amp;quot;Control&amp;quot;:
      case &amp;quot;Meta&amp;quot;:
      case &amp;quot;Shift&amp;quot;:
      case &amp;quot;v&amp;quot;:
      case &amp;quot;r&amp;quot;:
        return;
      case &amp;quot;Enter&amp;quot;:
        return (S.preventDefault(), void this.emailVerificationCallback());
      case &amp;quot;Backspace&amp;quot;:
        return (this.setCode(_, &amp;quot;&amp;quot;, &amp;quot;backward&amp;quot;), void S.preventDefault());
      case &amp;quot;ArrowLeft&amp;quot;:
      case &amp;quot;ArrowDown&amp;quot;:
        return void this.goBackward(_);
      case &amp;quot;ArrowUp&amp;quot;:
      case &amp;quot;ArrowRight&amp;quot;:
      case &amp;quot;Tab&amp;quot;:
        return void (_ !== this.codeLength - 1 &amp;amp;&amp;amp; (this.goForward(_), S.preventDefault()));
    }
    S.preventDefault();
    const O = Number(S.key);
    isNaN(O) ? (this.getInput(_).value = &amp;quot;&amp;quot;) : this.setCode(_, String(O));
  }&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;It appears that this function intercepts key presses and collects the verification code into a local JavaScript variable.
So there was no chance to modify the input form in the DOM after the fact, one would have to find the JavaScript variable and inject it there!&lt;/p&gt;&lt;p&gt;The critical part is that modifier keys are rejected.&lt;/p&gt;&lt;p&gt;And my problem is… that I’m using an &lt;a href=&quot;https://en.wikipedia.org/wiki/AZERTY&quot;&gt;AZERTY&lt;/a&gt; standard French keyboard layout, where digits are only accessible behind the Shift key.
In other words: blocking the Shift key means blocking digits.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://gendignoux.com/blog/images/input-digits/AZERTY.2MiSgW6cY72Z.svg&quot;&gt;&lt;img src=&quot;https://gendignoux.com/blog/images/input-digits/AZERTY.2MiSgW6cY72Z.svg&quot; alt=&quot;AZERTY keyboard layout&quot; title=&quot;&quot;/&gt;&lt;/a&gt;
Source: &lt;a href=&quot;https://en.wikipedia.org/wiki/File:KB_France.svg&quot;&gt;https://en.wikipedia.org/wiki/File:KB_France.svg&lt;/a&gt;, license: &lt;a href=&quot;https://creativecommons.org/licenses/by-sa/3.0/deed.en&quot;&gt;CC BY-SA 3.0&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;It turns out that AZERTY is one of the few &lt;a href=&quot;https://en.wikipedia.org/wiki/Keyboard_layout&quot;&gt;keyboard layouts in the world&lt;/a&gt; to have digits behind the Shift key.
At work I’m using an English (&lt;a href=&quot;https://en.wikipedia.org/wiki/QWERTY&quot;&gt;QWERTY&lt;/a&gt;) keyboard layout, and none of the people I asked were French, so obviously among the tested computers only my two “broken” laptops were affected.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://gendignoux.com/blog/images/input-digits/QWERTY.3_pfJz4KTSb5.svg&quot;&gt;&lt;img src=&quot;https://gendignoux.com/blog/images/input-digits/QWERTY.3_pfJz4KTSb5.svg&quot; alt=&quot;QWERTY keyboard layout&quot; title=&quot;&quot;/&gt;&lt;/a&gt;
Source: &lt;a href=&quot;https://en.wikipedia.org/wiki/File:KB_United_States.svg&quot;&gt;https://en.wikipedia.org/wiki/File:KB_United_States.svg&lt;/a&gt;, license: &lt;a href=&quot;https://creativecommons.org/licenses/by-sa/3.0/deed.en&quot;&gt;CC BY-SA 3.0&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Switching the keyboard layout at the software level in my operating system indeed fixed the problem on my “broken” laptops, and conversely switching to a French keyboard layout on the “working” devices triggered the bug.&lt;/p&gt;&lt;p&gt;At this point you may wonder how come a Swiss app doesn’t work on a French keyboard: isn’t French one of the official languages in Switzerland?
The answer is that the standard Swiss-French keyboard layout is of the &lt;a href=&quot;https://en.wikipedia.org/wiki/QWERTZ&quot;&gt;QWERTZ&lt;/a&gt; family (common in Central Europe), to allow efficient typing in both French and German.
Of course, digits don’t require pressing the Shift key with this layout.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://gendignoux.com/blog/images/input-digits/QWERTZ.MJ3ZjoG1_3PK.svg&quot;&gt;&lt;img src=&quot;https://gendignoux.com/blog/images/input-digits/QWERTZ.MJ3ZjoG1_3PK.svg&quot; alt=&quot;QWERTZ keyboard layout&quot; title=&quot;&quot;/&gt;&lt;/a&gt;
Source: &lt;a href=&quot;https://en.wikipedia.org/wiki/File:KB_Swiss.svg&quot;&gt;https://en.wikipedia.org/wiki/File:KB_Swiss.svg&lt;/a&gt;, license &lt;a href=&quot;https://commons.wikimedia.org/wiki/Commons:GNU_Free_Documentation_License,_version_1.2&quot;&gt;GFDL 1.2&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Still, there are about 171’000 residents of French nationality in Switzerland &lt;a href=&quot;https://www.bfs.admin.ch/bfs/en/home/statistics/population/migration-integration/foreign/composition.html&quot;&gt;according to the Swiss Federal Statistical Office&lt;/a&gt;, not to mention cross-border workers that may have to interact with the Swiss government.
So statistically speaking I’m surprised that this bug hasn’t been noticed and fixed yet!&lt;/p&gt;&lt;h2&gt;Conclusion&lt;/h2&gt;&lt;p&gt;After switching my keyboard layout, I could finally validate my email address, and register with a security key.&lt;/p&gt;&lt;p&gt;The weirdest part is that my actual identity was never verified in the process!
I simply entered my name, phone number and date of birth in the form but these were never verified.
The only things that the AGOV system validated is that (1) the email address exists and (2) the second factor is a security key from a model they allow or the AGOV Access app running on an Android or iOS device they allow.&lt;/p&gt;&lt;p&gt;This makes me question the threat model and overall usefulness of the AGOV system.
AGOV is resilient against account take-over by remote phishing – which is certainly important for privacy reasons!
However, nothing prevents someone else to create an account and put my name and other identity details there.&lt;/p&gt;&lt;blockquote&gt;
  &lt;p&gt;Upon &lt;a href=&quot;https://help.agov.ch/?c=autoident&amp;amp;l=en&quot;&gt;further investigation&lt;/a&gt;, it seems that identity validation is required to access services deemed sensitive.
So I just didn’t access a sensitive service yet.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;I’ve reported the bug on the help form, but haven’t received any reply yet.
Stay tuned!&lt;/p&gt;&lt;h2&gt;Post-mortem&lt;/h2&gt;&lt;p&gt;I think this incident deserves a little &lt;a href=&quot;https://sre.google/sre-book/postmortem-culture/&quot;&gt;post-mortem&lt;/a&gt;, to hopefully avoid similar ones in the future.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;What went poorly?&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;
  &lt;li&gt;Making a fancy UI with 6 input fields and a complex JavaScript logic isn’t a resilient way to build a UI.
Just use one native browser form field, and decorate it with CSS.&lt;/li&gt;
  &lt;li&gt;Completely decoupling the form logic from the DOM, rather than reading values from the DOM upon form submission.
An advanced user (or a browser extension) cannot even edit the DOM after the fact because the JavaScript logic has already intercepted the key presses.&lt;/li&gt;
  &lt;li&gt;Having only one support channel.
A second channel (email, phone, etc.) is best practice for resilience.&lt;/li&gt;
  &lt;li&gt;Using the same login logic for the main product and the support channel.
By coupling both, you miss bug reports from users who can’t even login.
There’s a reason why many providers of Internet services host their status page on a separate domain.&lt;/li&gt;
  &lt;li&gt;Gating support behind email verification.
Maybe you get less spam, but you also get fewer reports from genuine users.&lt;/li&gt;
  &lt;li&gt;Not publishing the source code &lt;em&gt;before&lt;/em&gt; the code is deployed and mandated by some systems.
The legal requirement to provide the source code isn’t just a compliance checkbox, it’s actually useful for users to troubleshoot issues and socialize bug reports and work-arounds.&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;What went well?&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;
  &lt;li&gt;The relevant JavaScript logic wasn’t obfuscated.
In the end a few string searches were enough to identify the bug.
No advanced decompiler, no expensive LLM required.
It would have cost me waaay more time to figure it out if the logic was obfuscated or compiled in WebAssembly.&lt;/li&gt;
  &lt;li&gt;The relevant code was distributed on the open web, making it easy to download it (even though in minified form) and inspect it for troubleshooting.
We may take this as a given, but it would have been more difficult to even access the code if it was distributed only for a few approved walled garden operating systems.&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;Where did I get lucky?&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;
  &lt;li&gt;Having multiple laptops at hand with various keyboard layouts ruled out many hypotheses early on.
A browser-specific or networking problem was unlikely.
That said, it took me a while to gather evidence in the initial phase, as I methodically took screenshots of each browser combination to make sure it wasn’t a mistake of my part.&lt;/li&gt;
&lt;/ul&gt;&lt;hr/&gt;&lt;div&gt;
  &lt;ol&gt;
    &lt;li&gt;
      &lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;example.com&lt;/code&gt; domain is reserved according to &lt;a href=&quot;https://tools.ietf.org/html/rfc6761&quot;&gt;RFC 6761&lt;/a&gt;. ↩&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;&lt;hr/&gt;&lt;h2&gt;Comments&lt;/h2&gt;&lt;p&gt;
            To react to this blog post please check
            the &lt;a href=&quot;https://infosec.exchange/@gendx/116913513746141001&quot;&gt; Mastodon thread&lt;/a&gt;, the &lt;a href=&quot;https://lobste.rs/s/yf6vbc/just_let_me_write_digits&quot;&gt;Lobste.rs thread&lt;/a&gt; and the &lt;a href=&quot;https://www.reddit.com/r/Switzerland/comments/1uvfohl/just_let_me_write_digits_failing_to_register_with/&quot;&gt; Reddit thread&lt;/a&gt;.
        &lt;/p&gt;&lt;hr/&gt;&lt;p&gt;
            &lt;a href=&quot;https://gendignoux.com/blog/feed.xml&quot;&gt; RSS&lt;/a&gt; |
            &lt;a href=&quot;https://infosec.exchange/@gendx&quot;&gt; Mastodon&lt;/a&gt; |
            &lt;a href=&quot;https://github.com/gendx&quot;&gt; GitHub&lt;/a&gt;
        &lt;/p&gt;&lt;hr/&gt;&lt;h2&gt;You may also like&lt;/h2&gt;&lt;p&gt;
            &lt;span&gt; &lt;a href=&quot;https://gendignoux.com/blog/2025/03/03/rust-interning-2000x.html&quot;&gt;The power of interning: making a time series database 2000x smaller in Rust&lt;/a&gt;&lt;/span&gt;
            &lt;br/&gt;
        
            &lt;span&gt; &lt;a href=&quot;https://gendignoux.com/blog/2024/03/14/website-refresh.html&quot;&gt;Making my website 10x smaller in 2024, with a dark mode&lt;/a&gt;&lt;/span&gt;
            &lt;br/&gt;
        
            &lt;span&gt; &lt;a href=&quot;https://gendignoux.com/blog/2024/11/18/rust-rayon-optimized.html&quot;&gt;Optimization adventures: making a parallel Rust workload 10x faster with (or without) Rayon&lt;/a&gt;&lt;/span&gt;
            &lt;br/&gt;
        
            &lt;span&gt; &lt;a href=&quot;https://gendignoux.com/blog/2023/03/27/single-transferable-vote.html&quot;&gt;STV-rs: Single Transferable Vote implementation in Rust&lt;/a&gt;&lt;/span&gt;
            &lt;br/&gt;
        
        
            &lt;span&gt;And &lt;a href=&quot;https://gendignoux.com/blog/&quot;&gt;35 more posts&lt;/a&gt; on this blog!&lt;/span&gt;
        
        &lt;/p&gt;</content:encoded>
</item>
<item>
<title>Query any HTML table with PRQL</title>
<link>https://avlasov.cabal.run/notes/001/index.html</link>
<guid isPermaLink="false">vkR1w0mOKRBf0U-8nFifwIUXpiBqxfj2b0_vmg==</guid>
<pubDate>Mon, 13 Jul 2026 19:17:18 +0000</pubDate>
<description>Query any HTML table with PRQL</description>
<content:encoded>&lt;body&gt;
&lt;header&gt;
&lt;h1&gt;Query any HTML table with PRQL&lt;/h1&gt;
&lt;p&gt;2026-07-08&lt;/p&gt;

&lt;/header&gt;
&lt;article&gt;
&lt;p&gt;Once again I find myself searching through a giant table on
Wikipedia. And again I think: it would be so much better if I could just
query it with SQL or something. Wish someone made a browser extension
for that!..&lt;/p&gt;
&lt;p&gt;Aaand now I did. Here is the source code: &lt;span&gt;&lt;a href=&quot;https://codeberg.org/aka_dude/tableq&quot;&gt;https://codeberg.org/aka_dude/tableq&lt;/a&gt;&lt;/span&gt;. And
&lt;span&gt;&lt;a href=&quot;https://addons.mozilla.org/en-US/firefox/addon/tableq/&quot;&gt;here&lt;/a&gt;&lt;/span&gt; you can get it for your Firefox. Except
this uses &lt;span&gt;&lt;a href=&quot;https://prql-lang.org/&quot;&gt;PRQL&lt;/a&gt;&lt;/span&gt; because it is much better that SQL.
Here is an example:&lt;/p&gt;
&lt;table&gt;
&lt;caption&gt;&lt;em&gt;&lt;span&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Comparison_of_file_systems#File_capabilities&quot;&gt;Source (Wikipedia)&lt;/a&gt;&lt;/span&gt;&lt;/em&gt;&lt;/caption&gt;
&lt;colgroup&gt;







&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;File system&lt;/th&gt;
&lt;th&gt;Block journaling&lt;/th&gt;
&lt;th&gt;Metadata-only journaling&lt;/th&gt;
&lt;th&gt;Case-sensitive&lt;/th&gt;
&lt;th&gt;Case-preserving&lt;/th&gt;
&lt;th&gt;File Change Log&lt;/th&gt;
&lt;th&gt;XIP&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/APFS&quot;&gt;APFS&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;td&gt;Optional&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/NTFS&quot;&gt;NTFS&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes (2000)&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Ext4&quot;&gt;ext4&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes, optional&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The &lt;code&gt;Q?&lt;/code&gt; button over upper-left corner activates the
extension. Upon activation, column names are renamed to snake_case and a
PRQL prompt appears. Try writing the following there and see what
happens!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from t0
filter (file_change_log == &amp;#39;Yes&amp;#39;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that the table still contains original HTML, e.g. the links are
still there.&lt;/p&gt;
&lt;p&gt;Some caveats:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The extension doesn’t do anything with column duplicates, so
underlying DB (sqlite) will have a problem with that.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Since content is stored as HTML in the DB, operations over text
will work with it, not rendered representation. This may lead to weird
results when sorting or searching through rich text.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All caveats should (and maybe will) be taken care of, but current
functionality is enough for me.&lt;/p&gt;
&lt;/article&gt;

&lt;footer&gt;
  No &amp;quot;AI&amp;quot; was used for any text or code here.
&lt;/footer&gt;

&lt;/body&gt;</content:encoded>
</item>
<item>
<title>Today I Rescued 7,234 Old GIFs – Dan Q</title>
<link>https://danq.me/2026/07/10/rescuing-7234-gifs/</link>
<enclosure type="image/jpeg" length="0" url="https://danq.me/_q23-og/29658/0.png"></enclosure>
<guid isPermaLink="false">GSUgzlT_opMLT_GULnAsm2IUe2DgtIcTywM59Q==</guid>
<pubDate>Sun, 12 Jul 2026 08:32:27 +0000</pubDate>
<description>As Web standards have moved on, a particular 1990s library of GIFs has been hard to effectively archive and was at risk of being lost forever. So I swooped in, wrote some hacky code, and now a few thousand low-res images have been saved for posterity. Hurrah, I guess?</description>
<content:encoded>&lt;p&gt;
              This week, &lt;a href=&quot;https://glitchyzorua.github.io/&quot;&gt;GlitchyZorua&lt;/a&gt; brought &lt;a href=&quot;https://forum.melonland.net/index.php?topic=5789.0&quot;&gt;to my attention&lt;/a&gt; the &lt;a href=&quot;https://www.ibiblio.org/gio/iconbrowser/&quot;&gt;Ibiblio Icon Browser&lt;/a&gt;, a collection of many thousands of GIF icons curated in the 1990s by &lt;a href=&quot;https://www.ibiblio.org/gio/&quot;&gt;Gioacchino La Vecchia&lt;/a&gt;. Glitchy’s goal was to archive a copy of all of the icons, which was turning out to be… challenging.
            &lt;/p&gt;&lt;figure&gt;
              
              &lt;figcaption&gt;
                A more-90s website you’re unlikely to see today.
              &lt;/figcaption&gt;
            &lt;/figure&gt;&lt;p&gt;
              It &lt;em&gt;looks&lt;/em&gt; pretty simple: (a) an index page, leading to (b) 24 sub-index pages, leading to (c) 57 icon directory pages, representing (d) 114 icon collections, containing
              anywhere up to (e) 7,296 icons, mostly but not always 32×32 pixels. Right?
            &lt;/p&gt;&lt;p&gt;
              But the challenge comes when you try to go from a directory page to an icon file. It &lt;em&gt;looks&lt;/em&gt; like you’re clicking a link, but really you’re clicking… an imagemap.
            &lt;/p&gt;&lt;p&gt;
              &lt;a href=&quot;https://danq.me/2024/02/02/netscapes-untold-webstories/#client-side-imagemap&quot;&gt;I’ve talked about imagemaps before&lt;/a&gt;, but the essence of them is that you define &lt;em&gt;areas&lt;/em&gt; of an
              image that, when clicked, hyperlink to different places. The most-common way of doing these was always &lt;em&gt;client-side&lt;/em&gt; imagemaps, where the HTML code itself contained all of
              the coordinates and, crucially, the resulting destinations. But that’s not what kind of imagemap &lt;em&gt;this&lt;/em&gt; is.
            &lt;/p&gt;&lt;figure&gt;
              &lt;img src=&quot;https://bcdn.danq.me/_q23u/2026/07/Screenshot-2026-07-10-at-13.42.44-640x224.png&quot; alt=&quot;Demonstration using curl of a request to an image map URL, including a pair of coordinates as the query string, resulting in two different redirects as a result of two different coordinate pairs.&quot; title=&quot;&quot;/&gt;
              &lt;figcaption&gt;
                A server-side imagemap asks your browser to send the &lt;em&gt;pixel coordinates&lt;/em&gt; that were clicked-on, as the query string. In the case of this server, that gets decoded
                server-side and you’re redirected based on where you clicked.
              &lt;/figcaption&gt;
            &lt;/figure&gt;&lt;p&gt;
              This one’s a &lt;em&gt;server-side&lt;/em&gt; imagemap. The HTML code looks like this… and there are no URLs for the resulting library of GIF files anywhere to be seen:
            &lt;/p&gt;&lt;p&gt;
              Directory indexing is disabled, so we can’t just knock the image filename off the end of the URL and inspect. So how are we to get these images, short of manually, painstakingly,
              clicking on each one of them? That’s what GlitchyZorua was wondering when I turned up with some bright ideas…
            &lt;/p&gt;&lt;p&gt;
              (We’re clearly not the only people who struggled: archive.org hadn’t managed to collect a full set of the icons either.)
            &lt;/p&gt;&lt;p&gt;
              Fortunately, we can work out a little something about the gallery images. Exploration of the site shows that they’re always laid out in a grid of up to 8×8, with each (including its
              size information) occupying a space of 72×89 pixels:
            &lt;/p&gt;&lt;figure&gt;
              &lt;img src=&quot;https://bcdn.danq.me/_q23u/2026/07/ibiblio-reverse-engineer-coordinate-system.png&quot; alt=&quot;Gallery of 64 images with a particular row and column highlighted to show the boundaries of what&amp;#39;s believed to be a particular &amp;#39;hit target&amp;#39; within it.&quot; title=&quot;&quot;/&gt;
              &lt;figcaption&gt;
                A little experimentation shows that clicking anywhere within the intersection area results in a redirect to the same image.
              &lt;/figcaption&gt;
            &lt;/figure&gt;&lt;p&gt;
              The webserver seems to be running Apache, so it’s probably using something like &lt;a href=&quot;https://httpd.apache.org/docs/current/mod/mod_imagemap.html&quot;&gt;mod_imagemaps&lt;/a&gt; to manage its
              server-side imagemaps. We can imagine that somewhere on the server there’s probably a file that looks a bit like this, mapping rectangular coordinate pairs to redirect URLs:
            &lt;/p&gt;&lt;figure&gt;
              &lt;div&gt;
                &lt;pre&gt;&lt;span&gt;# icon3 images:&lt;/span&gt;
base destic3/
&lt;span&gt;#    filename  |  top left  |  bottom right&lt;/span&gt;
&lt;span&gt;# -------------+------------+----------------&lt;/span&gt;
rect &lt;a href=&quot;https://www.ibiblio.org/gio/iconbrowser/icons/destic3/49ers.gif&quot;&gt;49ers.gif&lt;/a&gt;&lt;span&gt;         &lt;/span&gt;&lt;span&gt;0,0&lt;/span&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;72,89&lt;/span&gt;
rect &lt;a href=&quot;https://www.ibiblio.org/gio/iconbrowser/icons/destic3/49ers1.gif&quot;&gt;49ers1.gif&lt;/a&gt;&lt;span&gt;       &lt;/span&gt;&lt;span&gt;73,0&lt;/span&gt;&lt;span&gt;           &lt;/span&gt;&lt;span&gt;145,89&lt;/span&gt;
rect &lt;a href=&quot;https://www.ibiblio.org/gio/iconbrowser/icons/destic3/4dos.2.gif&quot;&gt;4dos.2.gif&lt;/a&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;146,0&lt;/span&gt;&lt;span&gt;           &lt;/span&gt;&lt;span&gt;217,89&lt;/span&gt;
rect &lt;a href=&quot;https://www.ibiblio.org/gio/iconbrowser/icons/destic3/4dos.gif&quot;&gt;4dos.gif&lt;/a&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;218,0&lt;/span&gt;&lt;span&gt;           &lt;/span&gt;&lt;span&gt;289,89&lt;/span&gt;
&lt;span&gt;# ... and so on for all 64 images in this collection!&lt;/span&gt;
&lt;/pre&gt;
              &lt;/div&gt;
              &lt;figcaption&gt;
                I sincerely hope that La Vecchia had some automated process that he used to produce the thousands of lines of configuration that he needed, and he didn’t write his files by hand!
              &lt;/figcaption&gt;
            &lt;/figure&gt;&lt;p&gt;
              We don’t have access to those configuration files, but we can infer what hit areas they might have. If each hit area is 72×89 pixels, we can hit the centre of the top-left one at 36×44
              and then just keep adding on 72 and 89 pixels to permute the centrepoints of &lt;em&gt;all&lt;/em&gt; the hit areas.
            &lt;/p&gt;&lt;p&gt;
              In pseudocode, what we’d need to do is:
            &lt;/p&gt;&lt;ul&gt;
              &lt;li&gt;For each library from &lt;code&gt;1&lt;/code&gt; to &lt;code&gt;113&lt;/code&gt;,
                &lt;ul&gt;
                  &lt;li&gt;For each X coordinate from the set &lt;code&gt;{36, 108, 180, 252, 324, 396, 468, 540}&lt;/code&gt;
                    &lt;ul&gt;
                      &lt;li&gt;For each Y coordinate from the set &lt;code&gt;{44, 133, 222, 311, 400, 489, 578, 667}&lt;/code&gt;
                        &lt;ol&gt;
                          &lt;li&gt;Generate a URL of the form:
                            &lt;br/&gt;
                            &lt;code&gt;https://www.ibiblio.org/iconbin/imagemap/icon{library}?{x},{y}&lt;/code&gt;
                          &lt;/li&gt;
                          &lt;li&gt;Make a HTTP &lt;code&gt;HEAD&lt;/code&gt; request to that URL
                          &lt;/li&gt;
                          &lt;li&gt;If you get a HTTP &lt;code&gt;302&lt;/code&gt; (redirect) response code, record the resulting &lt;code&gt;Location:&lt;/code&gt;
                          &lt;/li&gt;
                        &lt;/ol&gt;
                      &lt;/li&gt;
                    &lt;/ul&gt;
                  &lt;/li&gt;
                &lt;/ul&gt;
              &lt;/li&gt;
            &lt;/ul&gt;&lt;p&gt;
              That gets us the URL of every one of the thousands of GIFs on the service. Next, we can use &lt;code&gt;wget&lt;/code&gt; to download each of them. Sorted!
            &lt;/p&gt;&lt;p&gt;
              But we can do one better: once we’ve &lt;em&gt;got&lt;/em&gt; all the icons, we can present them in a &lt;em&gt;new&lt;/em&gt; website. One without server-side image maps, and with a working search.
              So that’s what I did. &lt;a href=&quot;https://github.com/Dan-Q/ibiblio-icon-archive&quot;&gt;I hacked together a &lt;em&gt;very&lt;/em&gt;&lt;/a&gt; &lt;a href=&quot;https://github.com/Dan-Q/ibiblio-icon-archive&quot;&gt;basic
              static site generator&lt;/a&gt; using Ruby and ERB templates, that produces a gallery with pagination (mirroring the page numbers from the original), plus client-side search. And of course
              the whole repository can be cloned if you just want a copy of the icons for yourself:
            &lt;/p&gt;&lt;figure&gt;
              
              &lt;figcaption&gt;
                I think my modernised version of this icon library is, while basic, a huge improvement upon the original… (thanks in large part to the advancement of Web technologies in the
                intervening years!).
              &lt;/figcaption&gt;
            &lt;/figure&gt;&lt;p&gt;
              Anyway: if you’d like to browse the library in its &lt;em&gt;new&lt;/em&gt; form, it’s at &lt;a href=&quot;https://ibiblio-icon-archive.danq.dev/&quot;&gt;ibiblio-icon-archive.danq.dev&lt;/a&gt;. It… looks its age,
              but at least now it’s accessible to the world and able to be archived for posterity.
            &lt;/p&gt;&lt;div&gt;
               &lt;data&gt;
              &lt;data&gt; &lt;data&gt; &lt;data&gt;
            &lt;/data&gt;&lt;/data&gt;&lt;/data&gt;&lt;/data&gt;&lt;/div&gt;</content:encoded>
</item>
<item>
<title>The Proportional Web</title>
<link>https://owickstrom.github.io/the-proportional-web/</link>
<guid isPermaLink="false">lE2LaE1zmRRaEzgK9sdYlQv2A3BhTWDgopWUbA==</guid>
<pubDate>Sun, 12 Jul 2026 08:32:27 +0000</pubDate>
<description>The Proportional Web</description>
<content:encoded>&lt;header&gt;&lt;h1&gt;The Proportional Web&lt;/h1&gt;&lt;p&gt;Inspired by the &lt;em&gt;The Elements of Typographic Style&lt;/em&gt;&lt;/p&gt;&lt;p&gt;Authored by &lt;cite&gt;&lt;a href=&quot;https://wickstrom.tech&quot;&gt;Oskar Wickström&lt;/a&gt;&lt;/cite&gt;. &lt;span&gt;v0.1.0&lt;/span&gt;, licensed under &lt;abbr&gt;MIT&lt;/abbr&gt;.&lt;/p&gt;&lt;/header&gt;&lt;h1&gt;Foreword&lt;/h1&gt;&lt;p&gt;Two summers ago, just before I started working at TigerBeetle, I picked up a new side project during downtime. I’ve always had a soft spot for &lt;abbr&gt;CSS&lt;/abbr&gt; but I don’t know why; frankly, it’s weird, confusing, and infamously error-prone. It could be nostalgia from my early days of programming web applications. Whatever the cause, &lt;a href=&quot;https://owickstrom.github.io/the-monospace-web/&quot;&gt;The Mono­space Web&lt;/a&gt; was born out of that love, and it took off way harder than I ever thought it would, with many personal blogs and even application interfaces having adopted it.&lt;/p&gt;&lt;p&gt;So, here I am again, spending weekend spare hours in a form of meditative state writing &lt;abbr&gt;CSS&lt;/abbr&gt;. This time, inspiration struck after reading Robert Bring­hurst’s classic &lt;em&gt;The Elements of Typographic Style&lt;/em&gt;. A challenge indeed, trying to implement the layout and typography of the book itself in the browser. Reckless, some might say! Surely the same rules don’t apply across print and web, where the latter cannot lean on a fixed page size, but has all the capabilities of a programmable platform. Perhaps, perhaps not. I’ve decided to publish regardless, and bid you to take from it what you will; if you find it pleasant or useful, that’s a wonder, and if not, that’s fine.&lt;/p&gt;&lt;p&gt;Consider this the spiritual and variable-width sequel of &lt;span&gt;The Mono­space Web&lt;/span&gt;, and equally open for reuse. I’m a sucker for Pandoc, and that’s what I’ve used to produce this HTML, but the stylesheet should work in many other settings with minor tweaks.&lt;/p&gt;&lt;h1&gt;&lt;span&gt;1&lt;/span&gt; Foundations&lt;/h1&gt;&lt;blockquote&gt;&lt;p&gt;Typography is the craft of endowing human language with a durable visual form.&lt;/p&gt;&lt;footer&gt;&lt;span&gt;Robert Bringhurst&lt;/span&gt;, &lt;cite&gt;The Elements of Typographic Style&lt;/cite&gt;, 2nd edition, &lt;span&gt;2002&lt;/span&gt;&lt;/footer&gt;&lt;/blockquote&gt;&lt;h2&gt;&lt;span&gt;1.1&lt;/span&gt; Typography&lt;/h2&gt;&lt;h3&gt;&lt;span&gt;1.1.1&lt;/span&gt; A single versatile font as the basis of the design&lt;/h3&gt;&lt;p&gt;In this document and its design, I’m using two variants of the &lt;span&gt;Alegreya&lt;/span&gt; font. The regular variant is used for body text and third-level headings. Its small-caps variant, &lt;span&gt;Alegreya SC&lt;/span&gt;, is used for titling-caps top-level headings, small-caps second-level headings, and for inline abbreviations such as &lt;abbr&gt;HTML&lt;/abbr&gt;. Finally, &lt;span&gt;Courier Prime&lt;/span&gt; is used for monospace code snippets.&lt;/p&gt;&lt;p&gt;Bringhurst argues in his book for choosing a single versatile typeface rather than a hodgepodge of different ones. I think &lt;span&gt;Alegreya&lt;/span&gt; is such a choice, and an excellent one at that.&lt;/p&gt;&lt;h3&gt;&lt;span&gt;1.1.2&lt;/span&gt; A sizing system built on relative measurements&lt;/h3&gt;&lt;p&gt;Every size is based on the root font size, which is 16px. Sizes are thus given in &lt;code&gt;rem&lt;/code&gt; units, relative to the root font size. The following table shows how fractional, &lt;code&gt;rem&lt;/code&gt;, and pixel measurements correlate.&lt;/p&gt;&lt;p&gt;Sizing everything based on the root font size makes it easy to scale the design, for instance on smaller viewports:&lt;/p&gt;&lt;p&gt;The line height is 1.2rem, and is used as the basis for vertical alignment of all elements. Much like in &lt;span&gt;The Mono­space Web&lt;/span&gt; — but not to the same extremes — I’ve tried to get everything globally aligned to multiples of the line height.&lt;/p&gt;&lt;h3&gt;&lt;span&gt;1.1.3&lt;/span&gt; Justified text in modern browsers&lt;/h3&gt;&lt;p&gt;As in Bringhurst’s book, body text is justified, not ragged right. To some, this is grave heresy on the web. &lt;em&gt;Thou shalt not justify.&lt;/em&gt; That’s what we’ve all been taught. But browsers have improved over time and today it’s not unthinkable to justify text. This stylesheet uses &lt;code&gt;word-break&lt;/code&gt;, &lt;code&gt;text-wrap&lt;/code&gt;, and &lt;code&gt;hyphens&lt;/code&gt; to control how words are broken and hyphenated at line breaks. The &lt;code&gt;hyphenate-limit-chars&lt;/code&gt; property is useful to control the bounds of hyphenation.&lt;/p&gt;&lt;p&gt;The risk is of course getting horrible word spacing and &lt;em&gt;rivers&lt;/em&gt; of whitespace in your paragraphs. If justified text doesn’t work for your uses, consider &lt;code&gt;text-align: left&lt;/code&gt; instead. In this document I find that it works acceptably with the line lengths, font size, and the content itself. I’ve also inserted a few soft hyphens to further guide the word breaks for some tricky words.&lt;/p&gt;&lt;h3&gt;&lt;span&gt;1.1.4&lt;/span&gt; Indented paragraphs for legibility&lt;/h3&gt;&lt;p&gt;In keeping with tradition, each successive paragraph is indented 3ch, which is the width of three 0 (0x30) characters. As an example, the paragraph following this one leads with an indent.&lt;/p&gt;&lt;p&gt;This lets your eyes more easily scan the structure of the text and find the starts and ends of paragraphs. We’ve done this in print text for at least half a millennium, and while the web has largely settled on no indent and vertical space between paragraphs, it is still a valid approach.&lt;/p&gt;&lt;h2&gt;&lt;span&gt;1.2&lt;/span&gt; Colors&lt;/h2&gt;&lt;p&gt;You may have noticed that this design is devoid of color. It’s all black on white. Not only am I personally inclined towards this minimalism in prose-heavy documents, at least as a strong default that I depart from only with careful consideration, but it’s also what Bringhurst argues in his book, although with print media in mind. I find it aesthetically pleasing and easier on the eyes, and it reserves the arsenal of color, and thus the attention of the reader, for the most critical things, such as diagrams conveying complex data.&lt;/p&gt;&lt;h1&gt;&lt;span&gt;2&lt;/span&gt; Elements&lt;/h1&gt;&lt;p&gt;This document uses a few extra classes here and there, but mostly it’s just semantic &lt;abbr&gt;HTML5&lt;/abbr&gt; markup. This, for instance, is a regular paragraph. The examples below are indented for clarity; in normal use they’d span the full width of the text.&lt;/p&gt;&lt;h2&gt;&lt;span&gt;2.1&lt;/span&gt; Headings&lt;/h2&gt;&lt;p&gt;The design provides styles for three level of headings: top-level chapter headings (&lt;code&gt;h1&lt;/code&gt;), section headings (&lt;code&gt;h2&lt;/code&gt;), and sub-section headings (&lt;code&gt;h3&lt;/code&gt;).&lt;/p&gt;&lt;figure&gt;&lt;h1&gt;&lt;span&gt;1&lt;/span&gt; On the theory of war&lt;/h1&gt;&lt;h2&gt;&lt;span&gt;1.2&lt;/span&gt; Art or science of war&lt;/h2&gt;&lt;h3&gt;&lt;span&gt;1.2.1&lt;/span&gt; Usage still unsettled&lt;/h3&gt;&lt;/figure&gt;&lt;h2&gt;&lt;span&gt;2.2&lt;/span&gt; Emphasis&lt;/h2&gt;&lt;p&gt;There’s no surprise here; text in &lt;code&gt;&amp;lt;em&amp;gt;&lt;/code&gt; tags is italicized, just as you’d expect:&lt;/p&gt;&lt;figure&gt;Do you think I can stay to become &lt;em&gt;nothing&lt;/em&gt; to you?&lt;/figure&gt;&lt;h2&gt;&lt;span&gt;2.3&lt;/span&gt; Horizontal line breaks&lt;/h2&gt;&lt;p&gt;There’s no clear guidance on horizontal line breaks in Bringhurst’s book, but it seems to me like a good place for ornamentation. Here’s one:&lt;/p&gt;&lt;figure&gt;&lt;hr/&gt;&lt;/figure&gt;&lt;p&gt;The symbol used is U+2767 &lt;span&gt;Rotated Floral Heart Bullet&lt;/span&gt; from the Unicode &lt;em&gt;Dingbats&lt;/em&gt; block.&lt;/p&gt;&lt;h2&gt;&lt;span&gt;2.4&lt;/span&gt; Details&lt;/h2&gt;&lt;p&gt;We can hide stuff in the &lt;code&gt;&amp;lt;details&lt;/code&gt;&amp;gt; element. Click the label below:&lt;/p&gt;&lt;figure&gt;&lt;details&gt;&lt;summary&gt;License&lt;/summary&gt;&lt;p&gt;Copyright 2026 Oskar Wickström&lt;/p&gt;&lt;p&gt;Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:&lt;/p&gt;&lt;p&gt;The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.&lt;/p&gt;&lt;p&gt;THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.&lt;/p&gt;&lt;/details&gt;&lt;/figure&gt;&lt;h2&gt;&lt;span&gt;2.5&lt;/span&gt; Asides&lt;/h2&gt;&lt;p&gt;Bringhurst’s book uses plenty of side notes. In &lt;abbr&gt;HTML&lt;/abbr&gt; we define those using &lt;code&gt;&amp;lt;aside&amp;gt;&lt;/code&gt; elements. Along with this paragraph there’s a side note. With a large enough viewport, you’ll see it in the right margin, aligned with the top of the previous paragraph; with a smaller viewport, it’ll be collapsed into an inline paragraph with ornamentation.&lt;/p&gt;&lt;h2&gt;&lt;span&gt;2.6&lt;/span&gt; Names&lt;/h2&gt;&lt;p&gt;Proper nouns and canonical names, distinguished using the &lt;code&gt;.canonical-name&lt;/code&gt; class, are rendered as small-caps much like in the example from Bringhurst’s book:&lt;/p&gt;&lt;figure&gt;… on the islands of &lt;span&gt;Lombok&lt;/span&gt;, &lt;span&gt;Bali&lt;/span&gt;, &lt;span&gt;Flores&lt;/span&gt;, &lt;span&gt;Timor&lt;/span&gt; and &lt;span&gt;Sulawesi&lt;/span&gt;, the same textiles …&lt;/figure&gt;&lt;h2&gt;&lt;span&gt;2.7&lt;/span&gt; Blockquotes&lt;/h2&gt;&lt;p&gt;Bringhurst talks about various ways of typesetting quotations, and the one I landed on is using an indented block, quotation marks around the text, and a footer with &lt;em&gt;author&lt;/em&gt;, &lt;em&gt;work&lt;/em&gt;, and &lt;em&gt;year&lt;/em&gt;.&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;Je n’ai fait celle-ci plus longue que parce que je n’ai pas eu le loisir de la faire plus courte.&lt;/p&gt;&lt;footer&gt;&lt;span&gt;Blaise Pascal&lt;/span&gt;, &lt;cite&gt;Lettres Provinciales&lt;/cite&gt;, letter XVI, &lt;span&gt;1657&lt;/span&gt;&lt;/footer&gt;&lt;/blockquote&gt;&lt;h2&gt;&lt;span&gt;2.8&lt;/span&gt; Figures&lt;/h2&gt;&lt;p&gt;Images with captions are put in &lt;code&gt;&amp;lt;figure&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;figcaption&amp;gt;&lt;/code&gt; elements, respectively. Similar to blockquotes, &lt;em&gt;author&lt;/em&gt; and &lt;em&gt;work&lt;/em&gt; are styled specifically using small-caps and italic text, and they share the indentation.&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://owickstrom.github.io/the-proportional-web/src/demo/vitruvian-man.jpg&quot; alt=&quot;&quot; title=&quot;&quot;/&gt;&lt;figcaption&gt;&lt;span&gt;Leonardo Da Vinci&lt;/span&gt;, &lt;cite&gt;Vitruvian Man&lt;/cite&gt;, &lt;span&gt;c. 1490, pen and watercolor over metalpoint on paper, 34.4 × 24.5 cm (photograph via &lt;a href=&quot;https://en.wikipedia.org/wiki/File:VitruvianMan_Leonardo_a.jpg&quot;&gt;Wikimedia Commons&lt;/a&gt;)&lt;/span&gt;&lt;/figcaption&gt;&lt;/figure&gt;&lt;h2&gt;&lt;span&gt;2.9&lt;/span&gt; Lists&lt;/h2&gt;&lt;p&gt;This is a plain old bulleted list:&lt;/p&gt;&lt;figure&gt;&lt;ul&gt;&lt;li&gt;Banana&lt;/li&gt;&lt;li&gt;Paper boat&lt;/li&gt;&lt;li&gt;Cucumber&lt;/li&gt;&lt;li&gt;Rocket&lt;/li&gt;&lt;/ul&gt;&lt;/figure&gt;&lt;p&gt;Ordered lists look pretty much as you’d expect:&lt;/p&gt;&lt;figure&gt;&lt;ol&gt;&lt;li&gt;Goals&lt;/li&gt;&lt;li&gt;Motivations&lt;ol&gt;&lt;li&gt;Intrinsic&lt;/li&gt;&lt;li&gt;Extrinsic&lt;/li&gt;&lt;/ol&gt;&lt;/li&gt;&lt;li&gt;Second-order effects&lt;/li&gt;&lt;/ol&gt;&lt;/figure&gt;&lt;h2&gt;&lt;span&gt;2.10&lt;/span&gt; Tables&lt;/h2&gt;&lt;p&gt;Tabular data is presented with a strong table head, using small-caps labels and a border. Otherwise it’s very simple, relying only on spacing.&lt;/p&gt;&lt;figure&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Name&lt;/th&gt;&lt;th&gt;Dimensions&lt;/th&gt;&lt;th&gt;Position&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Boboli Obelisk&lt;/td&gt;&lt;td&gt;1.41m × 1.41m × 4.87m&lt;/td&gt;&lt;td&gt;43°45′50.78″N 11°15′3.34″E&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Pyramid of Khafre&lt;/td&gt;&lt;td&gt;215.25m × 215.25m × 136.4m&lt;/td&gt;&lt;td&gt;29°58′34″N 31°07′51″E&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;&lt;h2&gt;&lt;span&gt;2.11&lt;/span&gt; Code Blocks&lt;/h2&gt;&lt;p&gt;This design is geared towards prose, not code-heavy technical writing, but code blocks and inline code should read well nonetheless. The font choice of &lt;span&gt;Courier Prime&lt;/span&gt; might feel a bit typewriter-nostalgic, but I find it goes very well with &lt;span&gt;Alegreya&lt;/span&gt; and the overall feel I’m aiming for. Here’s some code from the stylesheet:&lt;/p&gt;&lt;h1&gt;&lt;span&gt;3&lt;/span&gt; Usage&lt;/h1&gt;&lt;blockquote&gt;&lt;p&gt;Immature poets imitate; mature poets steal.&lt;/p&gt;&lt;footer&gt;&lt;span&gt;T.S. Eliot&lt;/span&gt;, &lt;cite&gt;The Sacred Wood&lt;/cite&gt;, &lt;span&gt;1920&lt;/span&gt;&lt;/footer&gt;&lt;/blockquote&gt;&lt;h2&gt;&lt;span&gt;3.1&lt;/span&gt; Purpose&lt;/h2&gt;&lt;p&gt;This design is meant for web documents comprised mainly of prose: books, journals, blogs, manuals, and the like. You might use this as a basis for your personal website, a specific project, or maybe a larger wiki. You probably don’t want it in a dynamic web application.&lt;/p&gt;&lt;p&gt;Copy or fork what you want, modify it to your heart’s content, but don’t forget proper attribution. The sources have license information in their headers, so it’s as easy as keeping those around.&lt;/p&gt;&lt;h2&gt;&lt;span&gt;3.2&lt;/span&gt; Getting started&lt;/h2&gt;&lt;p&gt;To get started quickly, download &lt;a href=&quot;https://github.com/owickstrom/the-proportional-web&quot;&gt;the sources from GitHub&lt;/a&gt;, put the directory in your project as &lt;code&gt;the-proportional-web&lt;/code&gt;, and add these to the &lt;code&gt;head&lt;/code&gt; element of your HTML:&lt;/p&gt;&lt;p&gt;If you’re using Pandoc, invoke it with some thing like the following set of arguments:&lt;/p&gt;&lt;p&gt;If you do need syntax highlighting, consider using a custom template like the &lt;a href=&quot;https://github.com/owickstrom/the-proportional-web/blob/main/src/demo/template.html&quot;&gt;one used for this HTML output&lt;/a&gt;, stripping away the default CSS included by Pandoc.&lt;/p&gt;&lt;h1&gt;&lt;span&gt;4&lt;/span&gt; Related works&lt;/h1&gt;&lt;h2&gt;&lt;span&gt;4.1&lt;/span&gt; The Elements of Typographic Style Applied to the Web&lt;/h2&gt;&lt;p&gt;The most closely related work that I know of is &lt;a href=&quot;https://webtypography.net/&quot;&gt;The Elements of Typographic Style Applied to the Web&lt;/a&gt; by Richard Rutter. It’s a website largely mirroring the structure of Bringhurst’s book, but adapting its advice to web design. It is much larger in scope and is seemingly focused on the information itself, while this document is a showcase of the ideas and of the stylesheet that is &lt;span&gt;The Proportional Web&lt;/span&gt;. Furthermore, I had a hard time reading Rutter’s website on my phone, with font sizes varying drastically. From what I’ve gathered, it dates back at least 20 years, and seems to have had no updates in about 8 years. I have put a lot of effort into making this design responsive yet consistent across viewport sizes. In any case, I think it’s fair to say that they serve different purposes and can happily coexist.&lt;/p&gt;&lt;h2&gt;&lt;span&gt;4.2&lt;/span&gt; Practical Typography&lt;/h2&gt;&lt;p&gt;Matthew Butterick’s &lt;a href=&quot;https://practicaltypography.com/&quot;&gt;Practical Typography&lt;/a&gt; is a real workhorse, and goes into sufficient detail on just about everything a layman or typography-adjacent engineer, like myself, will ever need. I have come back to it many times over the years.&lt;/p&gt;&lt;h2&gt;&lt;span&gt;4.3&lt;/span&gt; Tufte CSS&lt;/h2&gt;&lt;p&gt;&lt;a href=&quot;https://edwardtufte.github.io/tufte-css/&quot;&gt;Tufte CSS&lt;/a&gt; deserves an honorable mention. From what I’ve seen, it’s had a big impact and recognition. There are small details around typesetting that I don’t like in it, but it’s overall a solid piece of work.&lt;/p&gt;&lt;h1&gt;Afterword&lt;/h1&gt;&lt;p&gt;There we have it, &lt;em&gt;The Proportional Web&lt;/em&gt;. The methodology of Bringhurst’s book is rich and I highly recommend you read it. I’ve only scratched the surface in this exposition, and applied and adapted a small subset of his ideas.&lt;/p&gt;&lt;p&gt;This document was produced using &lt;a href=&quot;https://hackage.haskell.org/package/pandoc-cli&quot;&gt;Pandoc&lt;/a&gt;, &lt;a href=&quot;https://github.com/kangax/html-minifier&quot;&gt;html-minifier&lt;/a&gt;, &lt;a href=&quot;https://esbuild.github.io/&quot;&gt;esbuild&lt;/a&gt;, and &lt;a href=&quot;https://www.gnu.org/software/make/&quot;&gt;GNU Make&lt;/a&gt;. The design borrows heavily, both in ideas and actual layout, from &lt;em&gt;The Elements of Typographic Style&lt;/em&gt; by Robert Bringhurst. A big thanks to &lt;a href=&quot;https://x.com/usgraphics&quot;&gt;U.S. Graphics&lt;/a&gt; and &lt;a href=&quot;https://x.com/meekaale&quot;&gt;Mikael Brockman&lt;/a&gt; for reviewing my drafts.&lt;/p&gt;&lt;footer&gt;Copyright 2026 Oskar Wickström&lt;/footer&gt;</content:encoded>
</item>
<item>
<title>Headless Web</title>
<link>https://www.berger.team/glossar/headless-web/</link>
<guid isPermaLink="false">KpVQao84suKyQx_porrcORVoO0iPM0SEZWo2MA==</guid>
<pubDate>Sat, 11 Jul 2026 15:38:48 +0000</pubDate>
<description>Headless Web bedeutet, dass Datenbasis, Content-Backend, visuelles Frontend und externe Schnittstellen entkoppelt geplant werden. Der Inhalt liegt nicht fest in einer einzelnen Website-Oberfläche, sondern kann über APIs in Website, App, Shop, mehrsprachige Landingpages, KI-lesbare Datenquellen oder einen Agentic Workflow ausgespielt werden. Für KMU ist Headless Web keine reine Technologiefrage, sondern eine Architekturentscheidung. In meiner Arbeit […]</description>
<content:encoded>&lt;p&gt;Headless Web bedeutet, dass Datenbasis, Content-Backend, visuelles Frontend und externe Schnittstellen entkoppelt geplant werden. Der Inhalt liegt nicht fest in einer einzelnen Website-Oberfläche, sondern kann über APIs in Website, App, Shop, mehrsprachige Landingpages, KI-lesbare Datenquellen oder einen Agentic Workflow ausgespielt werden. Für KMU ist Headless Web keine reine Technologiefrage, sondern eine Architekturentscheidung. In meiner Arbeit […]&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://www.berger.team/wp-content/uploads/2025/11/20566-das-beste-aus-zwei-welten-spezialistenwissen-ohne-agentur-overhead.jpg&quot; alt=&quot;&quot; title=&quot;&quot;/&gt;&lt;/p&gt;</content:encoded>
</item>
</channel>
</rss>
