<?xml version="1.0" encoding="utf-8"?>
  <rss version="2.0"
    xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:wfw="http://wellformedweb.org/CommentAPI/"
    xmlns:dc="http://purl.org/dc/elements/1.1/"
    xmlns:atom="http://www.w3.org/2005/Atom"
    xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
    xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
    xmlns:georss="http://www.georss.org/georss"
    xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#"
  >
    <channel>
      <title>Piccalilli - Everything</title>
      <link>https://piccalil.li/</link>
      <atom:link href="https://piccalil.li/feed.xml" rel="self" type="application/rss+xml" />
      <description>We are Piccalilli. A publication dedicated to providing high quality educational content to level up your front-end skills.</description>
      <language>en-GB</language>
      <copyright>Piccalilli - Everything 2026</copyright>
      <docs>https://www.rssboard.org/rss-specification</docs>
      <pubDate>Fri, 26 Jun 2026 05:03:25 GMT</pubDate>
      <lastBuildDate>Fri, 26 Jun 2026 05:03:25 GMT</lastBuildDate>

      
      <item>
        <title>Publishing on the Atmosphere with Standard.site</title>
        <link>https://piccalil.li/blog/publishing-on-the-atmosphere-with-standardsite/?ref=main-rss-feed</link>
        <dc:creator><![CDATA[Declan Chidlow]]></dc:creator>
        <pubDate>Thu, 25 Jun 2026 11:55:00 GMT</pubDate>
        <guid isPermaLink="true">https://piccalil.li/blog/publishing-on-the-atmosphere-with-standardsite/?ref=main-rss-feed</guid>
        <description><![CDATA[<p><a href="https://standard.site/">Standard.site</a> provides a set of lexicons for publishing long-form content on the internet using the same protocol used under the hood by Bluesky.</p>
<p>If you are wondering what 'lexicons' and 'the Atmosphere' are, don't fret. This article will explain what they mean, why you should care about Standard.site, and walk you through exactly how you can implement Standard.site using some simple JavaScript or a plugin for your favourite content management system.</p>
<h2>What Standard.site is and why you should care</h2>
<p>If Bluesky is the network's answer to short-form microblogging, think of Standard.site as its equivalent for blogs, newsletters, and long-form journalism.</p>
<p>At its core, Standard.site is an open schema that dictates how articles and essays should be formatted as data. When you publish a blog post normally, it lives on your website and relies on scrapers or RSS feeds to be shared. By adopting the Standard.site lexicon, your long-form content becomes a natively understood piece of data on the decentralised web.</p>
<p>One of the most overt benefits we get from defining Standard.site lexicons for our publication are enhanced rich embeds on Bluesky, like this:</p>
<p><img src="https://piccalil.b-cdn.net/images/blog/standard-site-bluesky-post.png" alt="A mock bluesky post showing a linked blog post, featuring a richer UI experience with direct call to action to view publisher" /></p>
<p>We also get many other benefits. For example, I was surprised to see that the professional identity network Sifa ID <a href="https://sifa.id/p/vale.rocks#publications">displayed my posts upon my profile</a>. It was also pleasant to see my posts naturally populate on readers like <a href="https://pckt.blog/">pckt</a>, <a href="https://docs.surf/">Docs.surf</a>, <a href="https://potatonet.app/">potatonet</a>, and <a href="https://leaflet.pub/">Leaflet</a> without any additional work on my part.</p>
<p>The strength of AT Protocol is that data is interoperable and can be shared, which plays into the strength of Standard.site, which is that a single set of well structured-schemas. The result is that various indexers and tools can all work with the available data in their own ways, knowing how it'll be structured. Your content can be moved between hosts without losing your data or the audience you've built, and there is no single controlling authority. To get further into this, however, we must first establish an understanding of the AT Protocol.</p>
<p></p>
<h2>Understanding the AT Protocol</h2>
<p>To implement Standard.site, you will want to understand the Authenticated Transfer Protocol (known colloquially as the 'AT Protocol' or 'atproto') at least at a surface level. The AT Protocol is a decentralised system designed to give users ownership of their data.</p>
<p>To explain it at its simplest, you have a Personal Data Server (PDS) which hosts user accounts. Currently, the largest PDS is provided by Bluesky, but anyone can run their own. A PDS holds lots of user accounts, and each user account can hold records, which are data.</p>
<p>Each user account can be identified by a globally unique DID (Decentralised Identifier) that acts as their permanent ID. A DID looks like this: <code>did:plc:7qg6mz2xtzozxkgbcvf4pdnu</code>. Each account also has a handle, which comes in the form of a DNS record. We can see this in that people on Bluesky's PDS who haven't configured a custom domain for their account have a handle like this: <code>bsky.bsky.social</code>. If you navigate to that in a browser, it'll take you to the Bluesky page: <a href="https://bsky.bsky.social/"></a><a href="https://bsky.bsky.social">https://bsky.bsky.social</a>.</p>
<p>Each account features a data repository that holds collections of JSON records. These JSON records must follow specific structures called lexicons. Lexicons are just schemas, like JSON-Schema or OpenAPI, which define how the JSON must be structured and formatted. Records are put into 'collections', which we can think of as folders. A collection is identified by a Namespace Identifier (NSID) which makes reference to a domain to identify schemas.</p>
<p>Let’s run through an example with Bluesky so we can really get a handle on things. When a user signs up to Bluesky, a <code>self</code> record is created in the <code>app.bsky.actor.profile</code> collection of that user's data repository with information about the account, like its name and profile description. Piccalilli's looks something like this:</p>
<pre><code>{
  "uri": "at://did:plc:lyk2pixxcmyeu4jrapaq26fy/app.bsky.actor.profile/self",
  "cid": "bafyreihzo3igobmunvk6tmsaqgyatyw5gona4kiayvm2f62lymc5dzqjvu",
  "value": {
    "$type": "app.bsky.actor.profile",
    "createdAt": "2024-08-01T12:44:51.324Z",
    "description": "Level up your front-end skills. Stay for the approachable, friendly content and go away with transferable skills you can use day to day.",
    "displayName": "Piccalilli"
  }
}
</code></pre>
<p>Then, every time a post is made, or the Piccalilli account likes something, or blocks someone, or does anything else on Bluesky, a new record is created to represent that action. For instance, when Piccalilli reposts something, a new record is created under the <code>app.bsky.feed.repost</code> collection.</p>
<p><strong>Almost everything is a record, inside a collection, under a user account (identified by a DID), on a PDS.</strong></p>
<p>Notably, everything on ATProto is public. There is no concept of private records, which means we can go out and inspect or reference all the data on the protocol. There are a number of tools for inspecting AT Protocol data, but <a href="https://atproto.at/">Taproot</a> and <a href="https://pdsls.dev/">PDSLs</a> are my favourites. Search for your account handle, and you'll be greeted by your underlying records. Have a poke around to help wrap your head around the structure and how everything fits together.</p>
<p></p>
<h2>The two core records</h2>
<p>Now we've (hopefully) got at least (somewhat) of a (fledgeling) understanding of AT Protocol, we can start hooking up Standard.site. To get started with Standard.site, we need to create two specific types of records in your AT Protocol repository:</p>
<ol>
<li>A Publication Record, which defines information about our publication itself.</li>
<li>Document Records, which contain information about individual articles themselves.</li>
</ol>
<p>It is these records that Bluesky and the rest of the Atmosphere will reference. You can <a href="https://atproto.com/guides/writing-data#writing-data">create them any one of a number of ways</a>. AT Protocol is very open, and you can create records via a variety of methods, but for the purposes of this article, I'll be showing a JavaScript approach using the official <a href="https://npmx.dev/package/@atproto/api"><code>@atproto/api</code></a> npm package.</p>
<p>You will need to authenticate to create these records. The easiest way to do so is by creating an app password under Privacy and Security in Bluesky's settings. An app password gives access to your account and looks like this: <code>fg2g-xob3-xl78-5ezy</code>.</p>
<div><h2>FYI</h2>
<p>An app password gives <em>full</em> access to your account and bypasses multi-factor authentication. Be <em>extremely</em> careful with it. It is a secret, and you should take extreme care of it. This illustrative code shows including the app password inline, but you should consider putting it in an environment variable.</p>
<p>If you think your app password has been made public, you should revoke it, which can be done through the same interface you created it.</p>
</div>
<h3>Publication record</h3>
<p>The first step in support is having a publication record adhering to the <a href="https://standard.site/docs/lexicons/publication/"><code>site.standard.publication</code> lexicon</a>. You only need to create this record once per-publication.</p>
<p>Using the AT Protocol SDK, you can authenticate and create this underlying JSON record for your site with the script below, replacing the template values here with your publication's details.</p>
<p>For the purpose of illustration, this script only sets required properties.</p>
<pre><code>import { AtpAgent } from "@atproto/api";

// Initialise the agent (use your specific PDS if not on Bluesky)
const agent = new AtpAgent({ service: "&lt;https://bsky.social&gt;" });

async function createPublicationRecord() {
  // 1. Authenticate (Always use an App Password, never your main password)
  await agent.login({
    identifier: "your-handle.bsky.social",
    password: "your-app-password",
  });

  const did = agent.session.did;

  // 2. Define the Publication Record
  const publicationRecord = {
    $type: "site.standard.publication",
    url: "&lt;https://example.com&gt;",
    name: "My Awesome Blog",
  };

  // 3. Write the record to your repository
  try {
    const response = await agent.com.atproto.repo.createRecord({
      repo: did,
      collection: "site.standard.publication",
      record: publicationRecord,
    });

    console.log("Publication record created!");
    console.log("Your AT-URI is:", response.data.uri);
  } catch (error) {
    console.error("Failed to create publication:", error);
  }
}

createPublicationRecord();
</code></pre>
<p>If this script was successful, it should output a message reading 'Publication record created!', followed by an AT-URI. Save this, because we'll need it later. In the future if we need to amend this record, we can <a href="https://atproto.com/guides/writing-data#updating-records">revise the record directly</a>.</p>
<h3>Theming</h3>
<p>Though the above script is great, we can take it a bit further and add some more pizazz by <a href="https://standard.site/docs/lexicons/theme/">defining a theme</a>. You can create a theme to lend some more style to how your content displays in readers and how Bluesky embeds it. This is done by adding theming fields to your publication record. You need to specify a <code>background</code>, <code>foreground</code>, <code>accent</code>, and <code>accentForeground</code>. If you're setting any of these values, you must set <em>all</em> of them.</p>
<p>Bluesky uses <code>accent</code> and <code>accentForeground</code> like so:</p>
<p><img src="https://piccalil.b-cdn.net/images/blog/standard-site-theme-diagram.png" alt="A diagram pointing out the accent and accentForeground in the context of the call to action button" /></p>
<p>You should check that your foreground and background and accent and accent foreground all have appropriate contrast. Bluesky previously took these values directly, but now they do <a href="https://bsky.app/profile/esb.lol/post/3mnilfmgqns2d">some contrast adjustment of their own</a>.</p>
<p>You, unfortunately, cannot change the text which appears on the Bluesky embed button, which will always be 'View Publication' if you publish yourself. Some external Standard.site enabled services have their own special buttons with custom text and icons, but these are hard coded in the Bluesky client.</p>
<h3>Verifying your publication</h3>
<p>Next, we have the optional step of creating a file at <code>/.well-known/site.standard.publication</code> containing the AT-URL outputted by our record creation script. This verifies that your domain controls your publication record.</p>
<p>Most services, Bluesky included, don't require this verification. Indeed, some hosts might not let you write to the <code>/.well-known</code> path. However, if you <em>can</em> create a file named <code>site.standard.publication</code> within <code>/.well-known</code> and put your AT-URL within it, your publication will be more widely supported. This verification only needs to be done once.</p>
<p>You can check this has been created correctly by going to your URL on your site. For example, for my personal website, I can visit <a href="https://vale.rocks/.well-known/site.standard.publication">https://vale.rocks/.well-known/site.standard.publication</a> in my browser and see my AT-URI:</p>
<pre><code>at://did:plc:7qg6mz2xtzozxkgbcvf4pdnu/site.standard.publication/3mn2c332ulp2u
</code></pre>
<h3>Document records</h3>
<p>Now that your publication record exists, you need to create per-document records following the <a href="https://standard.site/docs/lexicons/document/"><code>site.standard.document</code> lexicon</a>. Every document needs its own record.</p>
<p>Standard.site supports having your document's content in the record, which is the approach that Offprint, pckt, Leaflet, and some other publishing platforms use. Whether you do this is up to you.</p>
<p>If you <em>do</em> include the content, then it can be displayed natively in Standard.site reader applications, and Bluesky embeds will provide a reading time estimate. For the purpose of this script, I'll again only be including required properties.</p>
<pre><code>import { AtpAgent } from "@atproto/api";
const agent = new AtpAgent({ service: "&lt;https://bsky.social&gt;" });

async function publishDocumentRecrd() {
  await agent.login({
    identifier: "your-handle.bsky.social",
    password: "your-app-password",
  });

  const did = agent.session.did;

  // 1. Define the Document Record
  const documentRecord = {
    $type: "site.standard.document",
    site: `at://your-did/site.standard.publication/your-pub-rkey`, // Full AT-URI of your publication
    title: "My New Post",
    publishedAt: "2026-06-11T00:00:00.000Z",
  };

  // 2. Write the record to your repository
  try {
    const response = await agent.com.atproto.repo.createRecord({
      repo: did,
      collection: "site.standard.document",
      record: documentRecord,
    });

    console.log("Success! Document record published to the Atmosphere:");
    console.log(response.data.uri);
  } catch (error) {
    console.error("Failed to publish document:", error);
  }
}

publishDocumentRecord();
</code></pre>
<p>If this script was successful, it should output a message reading 'Success! Document record published to the Atmosphere:', followed by an AT-URI. Save this, because we need it to verify the document.</p>
<h3>Verifying your document</h3>
<p>To complete the two-way verification, the HTML <code>&lt;head&gt;</code> of your live article must contain a link tag pointing back to the document record you just created:</p>
<pre><code>&lt;link rel="site.standard.document" href="at://did:plc:your-did/site.standard.document/the-record-rkey" /&gt;
</code></pre>
<p>Once these ends are tied together, your webpage and document record point to each other, and clients across the decentralised web can seamlessly reference the record.</p>
<h3>Adding images</h3>
<p>If you look through the Standard.site docs at all, you might notice references to icons and cover images. To make use of these, <a href="https://atproto.com/guides/images-and-video">we must upload a <em>blob</em></a>, which is what unstructured data (like images) within a repository are called.</p>
<p>We need to upload the blob first, so that we can refer to it with a reference in our record. Here is an example of uploading an image as a blob and then referencing it to use it as a cover image.</p>
<pre><code>import fs from "fs";
import { AtpAgent } from "@atproto/api";
const agent = new AtpAgent({ service: "&lt;https://bsky.social&gt;" });

async function uploadImageAndPublishDocument() {
  await agent.login({
    identifier: "your-handle.bsky.social",
    password: "your-app-password",
  });

  const did = agent.session.did;

  // 1. Read the local image file into a buffer
  const imageBuffer = fs.readFileSync("./path/to/your/cover.jpg");

  // 2. Upload the blob to your repository
  const { data: blobResponse } = await agent.com.atproto.repo.uploadBlob(
    imageBuffer,
    { encoding: "image/jpeg" }
  );

  console.log("Blob successfully uploaded!");

  // 3. Define the Document Record, attaching the returned blob reference
  const documentRecord = {
    $type: "site.standard.document",
    site: `at://your-did/site.standard.publication/your-pub-rkey`,
    title: "My New Post with a Cover Image",
    publishedAt: "2026-06-18T12:00:00.000Z",
    cover: blobResponse.blob, // This links the blob to your document
  };

  // 4. Write the document record to your repository
  try {
    const response = await agent.com.atproto.repo.createRecord({
      repo: did,
      collection: "site.standard.document",
      record: documentRecord,
    });

    console.log("Document record with cover image published:");
    console.log(response.data.uri);
  } catch (error) {
    console.error("Failed to publish document:", error);
  }
}

uploadImageAndPublishDocument();
</code></pre>
<p></p>
<h2>Setting up Standard.site on CMS platforms</h2>
<p>If writing JavaScript to push records manually sounds tedious, or you're already using a major content management system, you might prefer to have the process handled for you. How you integrate Standard.site depends very much on how your own site is built, and some platforms have ready-made integrations via plugins that handle all of the above behind the scenes:</p>
<ul>
<li><strong>WordPress:</strong> Has <a href="https://wordpress.org/plugins/atmosphere/">the ATmosphere plugin</a> and <a href="https://wordpress.wireservice.net/">the Wireservice plugin</a>.</li>
<li><strong>CraftCMS:</strong> Has <a href="https://plugins.craftcms.com/standard-site">a plugin simply called Standard.site</a>.</li>
<li><strong>Obsidian:</strong> Has a <a href="https://github.com/SootyOwl/obsidian-standard-site">community plugin</a>.</li>
<li><strong>Static sites</strong>: Generators like 11ty, Hugo, Astro, and Jekyll can use a dedicated CLI tool called <a href="https://sequoia.pub/">Sequoia</a>.</li>
</ul>
<h2>Checking it works</h2>
<p>Obviously, creating all these records and configuring all this Standard.site business is a bit useless if it doesn't actually work. The easiest way to test is by just plopping a link to a post on Bluesky and hoping it embeds, but if it doesn't, it can feel a tad opaque when it comes to figuring out what went wrong.</p>
<p>To rectify this, you can <a href="https://site-validator.fly.dev/">make use of Standard.site Validator</a> by <a href="https://octet-stream.net/">Thomas Karpiniec</a>. Consider returning to <a href="https://atproto.at/">Taproot</a> or <a href="https://pdsls.dev/">PDSLs</a> to study and review your records. Both will let you know if anything fails to validate and where things went awry.</p>
<h2>Further reading</h2>
<p>For some further reading, Piccalilli's own <a href="https://wil.to/">Mat Marquis</a>, creator of the <a href="https://piccalil.li/javascript-for-everyone">JavaScript for Everyone</a> course, has written his own posts documenting his <a href="https://wil.to/posts/standard-site/">understanding of</a> and <a href="https://wil.to/posts/implementing-standard-site/">implementation of</a> Standard.site on his own blog.</p>
<p>If you want to have multiple Standard.site publications under a single domain, then Jason Lengstorf has <a href="https://codetv.dev/blog/multiple-standard-site-publications-on-one-website">an in-depth guide on the Code.TV blog</a>.</p>
        
        ]]></description>
        
      </item>
    
      <item>
        <title>The Index: Issue #187</title>
        <link>https://piccalil.li/the-index/187/?ref=main-rss-feed</link>
        <dc:creator><![CDATA[Andy Bell]]></dc:creator>
        <pubDate>Fri, 19 Jun 2026 11:55:00 GMT</pubDate>
        <guid isPermaLink="true">https://piccalil.li/the-index/187/?ref=main-rss-feed</guid>
        <description><![CDATA[<h2><a href="https://hyperblam.how/?utm_source=the-index&amp;utm_medium=newsletter">Hyperblam</a></h2>
<p>Heydon Pickering has been <em>very</em> busy building out a declarative, web component-based system for making music with HTML, along with a <em>stunning</em> companion site.</p>
<h2><a href="https://vale.rocks/posts/game-console-browsers?utm_source=the-index&amp;utm_medium=newsletter">Web browsers on video game consoles</a></h2>
<p>A thoroughly fascinating read.</p>
<h2><a href="https://standard-reader.app/?utm_source=the-index&amp;utm_medium=newsletter">Standard Reader</a></h2>
<p>This is a really nice RSS reader-like <a href="https://standard.site/">standard.site</a> reader.</p>
<h2><a href="https://www.mohkohn.co.uk/writing/html-first/?utm_source=the-index&amp;utm_medium=newsletter">How building an HTML-first site doubled our users overnight</a></h2>
<p>We <a href="https://bsky.app/profile/piccalil.li/post/3mnucvdxpvc2m">challenged Alistair to write this</a> and boy, did they deliver!</p>
<h2><a href="https://piccalil.li/blog/an-in-depth-guide-to-customising-lists-with-css/?utm_source=the-index&amp;utm_medium=newsletter">An in-depth guide to customising lists with CSS</a></h2>
<p>Here's one from the <a href="https://piccalil.li/blog/">Piccalilli archives</a> that you might have missed to wrap up this issue.</p>
<hr />
<p>P.S. <a href="https://bell.bz/the-logical-destination-of-llms/?utm_source=the-index&amp;utm_medium=newsletter">I wrote a blog post for a change</a>.</p>
        
        <h2>Sponsor message</h2><a href="https://piccalilli.link/debugbear-ti-187"><img src="https://piccalil.b-cdn.net/images/ads/debugbear-c.jpg" alt="The DebugBear dashboard showing core web vitals, location metrics and page metrics." /></a><p><strong>Optimize visitor experience with DebugBear</strong></p>
<p>DebugBear real user monitoring tells you how fast your website is for your visitors and where you need to optimize.</p>
<p>Get in-depth diagnostics across Google’s Core Web Vitals metrics. For example, you can see what page elements and scripts are responsible for slow user interactions.</p>
<p><a href="https://piccalilli.link/debugbear-ti-187">Learn More</a></p>]]></description>
        
      </item>
    
      <item>
        <title>The Index: Issue #186</title>
        <link>https://piccalil.li/the-index/186/?ref=main-rss-feed</link>
        <dc:creator><![CDATA[Andy Bell]]></dc:creator>
        <pubDate>Mon, 15 Jun 2026 00:55:00 GMT</pubDate>
        <guid isPermaLink="true">https://piccalil.li/the-index/186/?ref=main-rss-feed</guid>
        <description><![CDATA[<h2><a href="https://henry.codes/writing/it-doesnt-matter-if-it-works/?utm_source=the-index&amp;utm_medium=newsletter">It doesn’t matter if it works</a></h2>
<p>An extremely good and important read.</p>
<h2><a href="https://joshcollinsworth.com/blog/productivity?utm_source=the-index&amp;utm_medium=newsletter">LLMs and performative productivity</a></h2>
<p>A rather detailed post that links out to some really interesting studies too.</p>
<h2><a href="https://thoughtbot.com/blog/when-to-use-and-not-use-css-shorthand-properties?utm_source=the-index&amp;utm_medium=newsletter">When to use (and not use) CSS shorthand properties</a></h2>
<p>As I see it, <code>margin</code>, <code>padding</code> and <code>border</code> are safe as houses but there is evil out there, such as the <code>flex</code> shorthand.</p>
<h2><a href="https://blog.jim-nielsen.com/2026/good-at-things/?utm_source=the-index&amp;utm_medium=newsletter">Being “good” at things</a></h2>
<p>Just a thoroughly delightful read, as per usual from Jim.</p>
<h2><a href="https://www.gamefile.news/p/panic-mail-arco-despelote-time-flies-thank-goodness-teeth?utm_source=the-index&amp;utm_medium=newsletter">The amazing mail sent to a video game publisher</a></h2>
<p>An interesting look behind the curtain that will make you smile.</p>
<h2><a href="https://piccalil.li/blog/my-favourite-3-lines-of-css/?utm_source=the-index&amp;utm_medium=newsletter">My favourite 3 lines of CSS</a></h2>
<p>Here's one from the <a href="https://piccalil.li/blog/">Piccalilli archives</a> that you might have missed to wrap up this issue.</p>
<hr />
<p>P.S. <a href="https://bsky.app/profile/bell.bz/post/3mntwiiu2dk2q">wanna see a glow up?</a>.</p>
        
        <h2>Sponsor message</h2><a href="https://piccalilli.link/debugbear-ti-186"><img src="https://piccalil.b-cdn.net/images/ads/debugbear-b.jpeg" alt="The DebugBear showing core web vitals, location metrics and page metrics." /></a><p><strong>How fast is your website?</strong></p>
<p>The free DebugBear website speed test provides an in-depth technical report on your page speed. You can also see how your Core Web Vitals have changed over time, based on Google’s Chrome User Experience Report.</p>
<p><a href="https://piccalilli.link/debugbear-ti-186">Test Your Website Speed</a></p>]]></description>
        
      </item>
    
      <item>
        <title>The Index: Issue #185</title>
        <link>https://piccalil.li/the-index/185/?ref=main-rss-feed</link>
        <dc:creator><![CDATA[Andy Bell]]></dc:creator>
        <pubDate>Fri, 05 Jun 2026 11:55:00 GMT</pubDate>
        <guid isPermaLink="true">https://piccalil.li/the-index/185/?ref=main-rss-feed</guid>
        <description><![CDATA[<h2><a href="https://eyeball.rory.codes/?utm_source=the-index&amp;utm_medium=newsletter">eyeball</a></h2>
<p>Incredibly addictive game. Best to use a mouse/trackpad than a touch device to give yourself a chance too!</p>
<h2><a href="https://dollarslice.nyc/?utm_source=the-index&amp;utm_medium=newsletter">Dollar Slice Surf Report, New York City</a></h2>
<p>A cool project by Scott Jehl as, <a href="https://scottjehl.com/posts/dollar-slice/">using pen, pencil, Procreate and Figma</a> as a much needed antidote to the slop era.</p>
<h2><a href="https://ffconf.org/feeds/?utm_source=the-index&amp;utm_medium=newsletter">Speaker feeds</a></h2>
<p>FFconf have a <em>huge</em> library of previous talks and speakers. Now, you can discover their RSS feeds and follow them. Handy!</p>
<h2><a href="https://letsgetcreative.today/?utm_source=the-index&amp;utm_medium=newsletter">Let's get creative</a></h2>
<p>Folks love it when we share indexes of <em>cool stuff</em>, so here's another!</p>
<h2><a href="https://bluecorridors.org/explore/species?utm_source=the-index&amp;utm_medium=newsletter">Protecting Blue Corridors</a></h2>
<p>Some absolutely delightful design and data visualisation work here.</p>
<h2><a href="https://piccalil.li/blog/styling-tables-the-modern-css-way/?utm_source=the-index&amp;utm_medium=newsletter">Styling Tables the Modern CSS Way</a></h2>
<p>Here's one from the <a href="https://piccalil.li/blog/">Piccalilli archives</a> that you might have missed to wrap up this issue.</p>
<hr />
<p>P.S. <a href="https://lowmess.com/?utm_source=the-index&amp;utm_medium=newsletter">this is a good website</a> from <a href="https://personalsit.es/?utm_source=the-index&amp;utm_medium=newsletter">personalsit.es</a>.</p>
        
        <h2>Sponsor message</h2><a href="https://piccalilli.link/debugbear-ti-185"><img src="https://piccalil.b-cdn.net/images/ads/debugbear-a.jpeg" alt="The DebugBear showing core web vitals, location metrics and page metrics." /></a><p><strong>Identify and fix web performance issues with DebugBear.</strong></p>
<p>Is your website slower than it should be? DebugBear provides in-depth web performance insights based on synthetic tests and real user monitoring.</p>
<p>Detect pages with critical issues, get in-depth technical reports to identify solutions, and measure how performance impacts conversion rates.</p>
<p><a href="https://piccalilli.link/debugbear-ti-185">Start Optimizing Your Website</a></p>]]></description>
        
      </item>
    
      <item>
        <title>A Front-end developer’s guide to the hybrid mobile app development landscape</title>
        <link>https://piccalil.li/blog/a-front-end-developers-guide-to-the-hybrid-mobile-app-development-landscape/?ref=main-rss-feed</link>
        <dc:creator><![CDATA[Rachael Yomtoob]]></dc:creator>
        <pubDate>Thu, 04 Jun 2026 11:55:00 GMT</pubDate>
        <guid isPermaLink="true">https://piccalil.li/blog/a-front-end-developers-guide-to-the-hybrid-mobile-app-development-landscape/?ref=main-rss-feed</guid>
        <description><![CDATA[<p>Just as with every aspect of my life, I find it hard to identify my software development skills. At my heart, I am a developer, though I spent way too much time as a high school senior fretting about whether or not I’d become an engineer. On paper, my job title has been product owner for almost the same amount of time as engineer/developer, but I was still writing code and reviewing PRs. Then comes the question of what kind of developer am I? Web developer? Mobile developer? Front-end? Full-stack? So many titles, but not a single one that fully encompasses my experience. I can tell you one thing that I’m not is a back-end developer 😆</p>
<p>All that said, for the purposes of this article, I’m a front-end web developer who has spent their 7 year career building a product for testing accessibility of mobile apps. I’d like to give you — a front-end web developer, who may not have mobile experience — a quick run-through to the mobile apps built with web technologies landscape.</p>
<h2>What is a hybrid mobile app?</h2>
<p>As a front-end web developer, we’re used to our HTML content being rendered in the browser whether it’s being generated by a framework like React or Vue, or being served from an HTML file.</p>
<p>The mobile app world is a quite different story where the rendered content doesn’t follow agreed upon and maintained guidance, like web standards. For example, Apple and Google completely govern their own native tech stacks for building iOS and Android apps respectively. Those tech stacks create elements on the screen called <code>Views</code>. Then there’s the 3rd party cross-platform frameworks such as React Native, .NET MAUI (previously Xamarin), Flutter. These all allow you to build both an Android and iOS app, and sometimes a web and/or desktop app, from a single code base, but they each do that process a bit differently to each other.</p>
<p>React Native and .NET MAUI generate <code>Views</code> that mimic or are very similar to native mobile <code>Views</code>. They are designed to use technologies familiar to React and .NET developers: TypeScript/JavaScript with CSS and C# with XAML. Flutter is in it’s own league because it uses Dart to build <code>Widgets</code> that are rendered by it’s own graphics engine, bypassing the concept of native <code>Views</code> entirely.</p>
<p>While all these frameworks are often referred to in discussions of hybrid mobile apps, I prefer to distinguish them as cross-platform or code-once frameworks and reserve hybrid to specifically describe the subset of apps which present a blend of web and mobile content.</p>
<p>We’ll define a hybrid mobile app as follows: “A mobile app which primarily features HTML content rendered in <code>WebViews</code> alongside some native mobile content or functionality”</p>
<p>A <code>WebView</code> is a native mobile <code>View</code> which acts as an embedded web browser component to render HTML content inside of it instead of native controls like sliders or buttons. The mobile browser apps such as Safari and Chrome utilize a <code>WebView</code> as the main component with the addition of peripherals like the URL bar and back button. That’s a route to web developers to feel <em>empowered</em> rather than hindered.</p>
<p>The cross-platform framework names I haven’t mentioned yet are by this definition, hybrid app development frameworks: Ionic, Capacitor, Cordova/PhoneGap. It’s a bit complicated to untangle what each framework is and does, so we’ll dive into that in the next section.</p>
<p></p>
<h2>Hybrid App Development Frameworks</h2>
<p>Let’s start by talking about Apache Cordova/Adobe PhoneGap, since it’s the original hybrid framework, created back in 2009. Here’s the overview statement on <a href="https://cordova.apache.org/docs/en/latest/guide/overview/index.html">the Cordova documentation site</a>:</p>
<blockquote>
<p>Apache Cordova is an open-source mobile development framework. It allows you to use standard web technologies - HTML5, CSS3, and JavaScript for cross-platform development. Applications execute within wrappers targeted to each platform, and rely on standards-compliant API bindings to access each device's capabilities such as sensors, data, network status, etc.</p>
</blockquote>
<p>There’s some history with the project being acquired by Adobe, including in an offering called PhoneGap, but that was discontinued in 2020. Cordova is still alive and actively maintained, but you may see references to PhoneGap while looking for hybrid app development topics.</p>
<p>Cordova isn’t a UI framework, but rather a runtime environment for your web app on a mobile device. This means you could take an existing web app that you built with your favorite front-end tools and package it up to render inside a <code>WebView</code> in both an iOS and Android app. Now there’s quite a lot of reasons why you wouldn’t want to to that, but let’s cover the other runtime frameworks first.</p>
<p>Capacitor is a newer option for a hybrid framework to ship your web content wrapped in a mobile app, developed in 2018 as a modern alternative to Cordova/PhoneGap. The intro on <a href="https://capacitorjs.com/docs">the Capacitor docs site</a> reads:</p>
<blockquote>
<p>Capacitor is a cross-platform native runtime that makes it easy to build performant mobile applications that run natively on iOS, Android, and more using modern web tooling. Representing the next evolution of Hybrid apps, Capacitor creates <strong>Web Native apps</strong>, providing a modern native container approach for teams who want to build web-first without sacrificing full access to native SDKs when they need it.</p>
</blockquote>
<p>What separates Capacitor from Cordova is that it’s built and maintained by the Ionic framework team. While the former are both native runtime frameworks, Ionic is actually a front-end UI framework for hybrid apps. Ionic can be used with both Cordova and Capacitor, but Capacitor is designed specifically to work in conjunction with Ionic. Here’s the introduction on <a href="https://ionicframework.com/docs">the Ionic docs site</a>:</p>
<blockquote>
<p>Ionic is an open source UI toolkit for building performant, high-quality mobile apps using web technologies — HTML, CSS, and JavaScript — with integrations for popular frameworks like <a href="https://ionicframework.com/docs/angular/overview">Angular</a>, <a href="https://ionicframework.com/docs/react/overview">React</a>, and <a href="https://ionicframework.com/docs/vue/overview">Vue</a>.</p>
</blockquote>
<p>Now we’re finally in the front-end developer’s domain! Before we dig into it too much, let’s go over some important design concepts for hybrid apps.</p>
<p></p>
<h2>Designing a Hybrid App</h2>
<p>Like I said earlier, you can use either Capacitor or Cordova, to wrap your existing web front-end into a <code>WebView</code> inside a mobile app, but there are some considerations to make since content built for the web is structured quite differently than content built for mobile.</p>
<p>For example, a common pattern in web apps is a single page with lot of content that can be scrolled to or jumped to with links/landmarks. Whereas, in a mobile app, content is often better presented in small chunks with navigation through a flow of multiple screens. Mobile app user experience is built around the fact that the content is displayed in a small viewport, but the web was originally built for desktop and adapted to suite mobile devices as their browser became more and more capable.</p>
<p>I’ve spoke with folks in the industry that claim most users can’t tell whether an app is presenting native content or a <code>WebView</code>, but I find that hard to believe. Maybe I’m biased because I know too much about both, but it says a lot if a user downloads your app instead of visiting your website since there’s a much higher barrier to entry. They likely took the extra effort to open the App Store or Play Store and remember or find their account password to install your app because they want a native mobile experience. One thing I can confidently say is that a screen reader user will definitely know the difference since it will literally announce <code>WebView</code> when they hit one, and the landmarks are different, like the fact that Android only has 1 level of heading compared to the 6 levels in HTML <code>&lt;h1&gt;-&lt;h6&gt;</code>.</p>
<p>So all that said, how do you build a stellar hybrid app user experience? By using the concepts of responsive and mobile-first web design of course!</p>
<p>Figma has <a href="https://www.figma.com/resource-library/mobile-first-design/#core-principles-of-the-mobile-first-design-process">a wonderful article</a> which covers the topic focusing on websites, but can be applied to hybrid apps as well:</p>
<blockquote>
<p>“Mobile-first website design flips the traditional Web development approach. Instead of starting with a desktop layout and scaling down, you begin with a mobile website design. This approach prioritizes smaller screens, slower Internet connections, and the needs of on-the-go users.”</p>
</blockquote>
<p>Another layer to mobile-first design is that Android and iOS each have a look and feel uniquely branded to Google and Apple. We’ve established that the web and mobile ecosystems are very different, but iOS and Android are almost just as different too. So you’re faced with some choices: design 2 experiences, pick iOS or Android and ship it to the other platform anyways, or roll your own that ideally caters to both. Trust me, I’ve seen it all, and my least favorite is an Android styled app shipped to iOS</p>
<p>While companies/brands love the idea of a consistent experience across platforms, users prefer your app to look behave like the rest on their device do. A lot of factors go into choosing a design pattern for an app such as target audience, development resource capacity, etc, so let’s set that aside and finally dig into building the bits</p>
<h2>Building a Hybrid app Front-end</h2>
<p>I want to bring us back to Ionic because <a href="https://ionicframework.com/docs/components">its UI component library</a> allowing front-end developers to build seamless experiences for both iOS and Android from a single codebase using technologies you know and love. It’s full of mobile-first components which render on iOS matching Apple’s aesthetics and on Android matching Material Design, Google’s open-source design system. These components are available in Angular, React, Vue, and plain ole JavaScript with beautiful interactive demos on the documentation site.</p>
<p>Let’s take a closer look at a Toggle component which is commonly used in the settings of a mobile app. In React, you import the <code>IonToggle</code> from Ionic’s library and use it just like you would any component for a web-first app.</p>
<pre><code>import React from 'react';
import { IonToggle } from '@ionic/react';

function Example() {
  return (
    &lt;&gt;
      &lt;IonToggle&gt;Default Toggle&lt;/IonToggle&gt;
      &lt;IonToggle checked={true}&gt;Checked Toggle&lt;/IonToggle&gt;
      &lt;IonToggle disabled={true}&gt;Disabled Toggle&lt;/IonToggle&gt;
      &lt;IonToggle checked={true} disabled={true}&gt;
        Disabled Checked Toggle
      &lt;/IonToggle&gt;
    &lt;/&gt;
  );
}
export default Example;
</code></pre>
<p>On iOS, you’ll get 4 toggles in Apple’s well known style.</p>
<p><img src="https://piccalil.b-cdn.net/images/blog/4-apple-toggle-elements.png" alt="4 iOS toggle elements in different states: default, checked, disabled and disabled checked" /></p>
<p>For Android, the recognisable material design elements are used.</p>
<p><img src="https://piccalil.b-cdn.net/images/blog/4-android-toggle-elements.png" alt="4 Android toggle elements in different states: default, checked, disabled and disabled checked" /></p>
<p>All of the Ionic components can be styled with CSS custom properties and standard CSS, so you can achieve that consistent experience across platforms by modifying the iOS and Android styles slightly or make them match exactly.</p>
<p></p>
<h2>Accessibility</h2>
<p>As digital accessibility advocate, I can’t write an article without touching on the topic. I think of accessibility in 2 realms: compliance and user experience.</p>
<p>The realm of compliance focuses on building digital content that adheres to the <a href="https://www.w3.org/TR/WCAG22/">Web Content Accessibility Guidelines (WCAG)</a>. These are the guidelines which legislature around the world reference, and large enterprises strive to meet. There isn’t a similar de facto standard for mobile app accessibility despite the immense difference in how content is rendered, but instead, there’s only adaptations and interpretations of WCAG for mobile.</p>
<p>On the other hand, Apple and Google put emphasis on accessibility through the user experience of the iOS and Android ecosystems. There’s <a href="https://developer.apple.com/design/human-interface-guidelines/accessibility">Apple’s Human Interface Guidelines with a foundations section dedicated to accessibility</a>, and <a href="https://developer.android.com/guide/topics/ui/accessibility">the Android Developer guide on accessibility.</a></p>
<p>There’s no straightforward way to make a perfectly accessible app today, but that’s ok! Just as with most things in life, it’s about finding the right balance. On the compliance side, that <code>WebView</code> inside a mobile app is rendering HTML, so WCAG criteria can be directly applied, so there’s certainly advantages for hybrid apps in that regard. However, accessibility compliance doesn’t always equate to a high quality user experience, which is where the advice from Apple and Google come into play.</p>
<h2>Wrapping up</h2>
<p>While the world of mobile apps can be intimidating to a front-end web developer, it’s certainly worth exploring, even to be aware of the landscape for if you find yourself in a project in the future.</p>
<p>With that in mind, I hope this article is a useful introduction and shows that you don’t have to step completely out of your comfort zone thanks to the concept of hybrid mobile apps, providing a very <em>familiar</em> experience. It makes them a useful jumping off point!</p>
        
        ]]></description>
        
      </item>
    
    </channel>
  </rss>
