Kitten Kawaii: porting a React library and Next.js web site to Kitten
Interactive embed (play with it)
I stumbled on Muiki Miu’s (Elizabet Oliveira’s) adorable React Kawaii web site and wanted to use one of the characters in my Kitten app.
Instead of converting just the SVG component for the character I needed, I thought it would be a fun experiment to port the whole of React Kawaii, including its Next.js web site, to Kitten and, in the process, hopefully illustrate some of the differences between Big Web and Small Web.
It turned out to be a hugely eye-opening and worthwhile experience.
Interactive embed (play with it)
Methodology and goals
At first, I thought I’d just clone the React Kawaii repository, which contains both the React Kawaii library and the web site, and remove all the bits I didn’t need before converting the bits I did.
Turns out, due to the nature of React and Next.js, with all of their development-time dependencies, etc., there was a lot of stuff I didn’t need.1
So I decided it would be easier to start from scratch with my port.
Furthermore, instead of creating a pixel-for-pixel copy, my goal was to create a Kitten port of React Kawaii that is functionally equivalent2 but designed to reflect the ideology and aesthetic of the Small Web.
So this port is as much about the philosophical differences between Big Web and Small Web as it is about the code differences between React and Kitten.
I hope you find it as interesting as I did and here’s a big thank-you to Elizabet for making and sharing the excellent React Kawaii library and web site as well as her lovely character designs, without which my port would not have been possible.
Getting started
One of the first things you notice as a developer when you try to run a React app locally is just how much you have to do to get started.
In contrast, getting started with Kitten is a breeze.
React Kawaii
Here are the steps you need to follow to get React Kawaii up and running locally on your own machine.
Prerequisites
-
Install (the correct version of) Node.js (in this case, 18.17.0 should work and a tool like nvm.fish can help with this)
-
Install the right version of pnpm. You can use npm to install pnpm (
npm i -g pnpm@8
).
Process
-
Clone the repository and enter the folder you cloned it into:
git clone https://github.com/miukimiu/react-kawaii.git cd react-kawaii
-
Install the dependencies (this takes about 18 seconds on my desktop machine):
pnpm i
-
Start the dev server (this is quick and only takes ~ 1.6 seconds):
pnpm run dev
-
Open your browser and hit http://localhost:3000 to view the page after waiting roughly 16 seconds for Next.js to compile the page.
All in all, not counting installing the prerequisites, it takes about half a minute of waiting for scripts to run and things to build before you can view the page in your browser.3
Web development doesn’t have to be this way.
Let’s see how Kitten does it.
Kitten Kawaii
Prerequisite
Install Kitten (one command to paste into terminal; takes under 20 seconds to install on my home connection).
Process
Run the app and open it in your default browser:
kitten run https://codeberg.org/aral/kitten-kawaii.git --open
Not counting installing Kitten, the above command takes roughly 3 seconds to open the fully functional web site locally in my browser.
That’s an order of magnitude difference in time.
But that’s not all.
It’s actually faster even if you install Kitten first.
In the above video, we run the following command to install Kitten on a Linux machine for the first time and then have it clone the Kitten repository, run the server, and serve the page:
wget -qO- https://kittens.small-web.org/install | bash; kitten run https://codeberg.org/aral/kitten-kawaii.git --open
Even when you first install Kitten itself, it takes only about 18 seconds for Kitten Kawaii to appear in the browser versus about 30 seconds for just React Kawaii without including the time it would take to install Node.js and pnpm.
And most of Kitten’s installation time is spent downloading Kitten’s runtime, which is – surprise surprise – Node.js.
Why such a difference?
Big Tech and Big Web
The frameworks and tools of the Big Web reflect the weight of the centralised infrastructure necessary to perpetuate the business models of the Big Tech corporations that designed them.
They’re designed to support the complex task of gathering as many people as possible in one place so as to farm them for their data.
This toxic baggage comes along for the ride even if all you’re doing is building a cute little app to configure some lovely vector characters.
Big Tech surveillance capitalists like Google, Facebook, etc., are factory farms for human beings. And their tools are designed to make it possible to build and run similar factory farms. If that’s not what you want to do, you should ask yourself why you’re using their tools and building things the way they tell you to.
So every time you wait 16 seconds as Next.js compiles your page after a server restart, use that time to think about why all that complexity exists and how it doesn’t have to be that way…
We can do better.
All we need to do is to build the opposite of the Big Web, which, quite naturally, is the Small Web.
Small Tech and Small Web
The Small Web, born out of the Small Technology principles, is about people owning and controlling their own web sites and apps.
It’s about seizing the means of communication, if you will, and building a peer-to-peer web that respects human rights.
All technology is political.
Technology that doesn’t appear to be political just happens to be the technology of the ruling class.
Building tools for people (not tools to farm people)
Instead of building factory farms for human beings, in Small Tech, we build tools for individuals. On the Small Web, we don’t even have the concept of ‘users’. We refer only to people.
When you build with Big Web tools, you’re using a stack designed to support the farming of 1 user, 2 users, or a million users or more. That’s orders of magnitude more complexity then when you use a Small Web stack designed to build tools owned and controlled by just one person.
This is why it takes 10× as long to get the React Kawaii web site up and running locally compared to doing the same thing with Kitten Kawaii.
It’s not because I’ve coded Kitten Kawaii to be 10× faster than React Kawaii.
It’s because React and Next.js are complex Big Web technologies designed to enable centralised factory farms at planet scale while Kitten is a simple Small Web tool designed for individuals to own and control their own technology.
It’s because Big Tech views human beings as resources while Small Tech views human beings as people and respects their rights, effort, and experience.
Hope 💕
Think about it:
React is built by a trillion-dollar corporation.
Next.js is made by a multi-billion-dollar corporation.
Kitten is authored by some guy who works at a two-person not-for-profit with no money to speak of by piecing together components contributed by a few hundred people with some of my own.
And it’s better at the things that matter.
I’m not just talking about speed or size here but the overall experience. Have you seen the Streaming HTML workflow? It’s rather sweet.
Then there’s the server provisioning and app deployment experience with Domain that you hopefully will be able to try out first hand in the coming months as Domain enters public beta.
Not to mention the fact that the Small Web will enable people to communicate using end-to-end encrypted encryption between sites.
In fact, about the only thing Kitten will not allow you to do is to build centralised apps that farm people for their data. It’s designed not to scale in that way.
If nothing else, let this give you hope that other ways of being are possible.
We can and will do things in ways that are better than what the Big Tech corporate mainstream tells us is the only alternative.
We will build a better web.
A web for people, not corporations or governments.
A Small Web.
Design
The first thing you notice when you play with both React Kawaii and Kitten Kawaii is how different they look.
React Kawaii is a beautiful example of a mainstream React-based web site. It uses multiple React-based components from the Radix UI library, which work perfectly well to let you pick colours and moods and adjust character sizes. The web site looks and feels like a modern, well-designed web site.
Kitten Kawaii, on the other hand, feels like a toy. (At least that’s what I was going for.) It lets you concentrate on individual characters and use a slider to move between moods (which I find rather fun). It also smoothly transitions to a details view if you’re interested in seeing the code.
Although Kitten Kawaii uses basic HTML components with very slight enhancements, it feels like a custom app. And part of that is because it doesn’t use a UI framework. By going back to first principles, we can design interfaces that are fine-tuned to our specific apps and sites and that don’t add unnecessary visual weight or complexity to them.4
Kitten has first-class support for components. If you look at the source code, you’ll see that the interface is composed of many tiny Kitten components, which are used both when initially rendering the page and, later, sent back to update the interface as needed using Kitten’s Streaming HTML workflow.
Look and feel aside, there are also other fundamental design decisions like maintaining state in the URL, a focus on accessibility, and avoiding lock-in.
So let’s dive in and take a look at specific design and development decisions.
Accessibility
One of my main bugbears of having Fedora Silverblue5 as my operating system on my main desktop computer is that Fedora has been shipping with a broken screen reader for the last ten years.
And it’s not just Fedora. Any Linux distribution that ships with Wayland by default ships with a broken screen reader. This includes the most popular ones like Debian, Ubuntu, and OpenSUSE.
So, if you’re using one of these operating systems for web development, you must also have either a Mac or a Windows machine (or a Windows VM, at the very least) in order to test out your work with a screen reader.
For Kitten Kawaii, I tested as per usual on my Mac and iOS using VoiceOver, as well as in a Windows VM with Narrator.
Linux folks love to hate Microsoft and Windows. And they’re right to as Windows is an ad-infested and surveillance-ridden dumpster fire of an operating system.
However, as horrible as Windows is, it does at least ship with an excellent screen reader by default. Which is more than can be said for some of the most popular Linux operating systems today that hundred-billion-dollar and hundred-million-dollar corporations like IBM and Canonical profit from.
How embarassing for them.
Hopefully this will change soon and I can more easily test as I develop on my main machine.
If you do notice anything that can be improved accessibility-wise or otherwise, please open an issue and let me know. Or just ping me on the fediverse if that’s easier for you.
URL-based state
One thing you might not notice initially is that the app keeps state in the URL. This feels right for the web.
To test it out, configure your character and hit refresh. You should see the interface load up in exactly the same state as you left it.
Here’s the component that handles URL state:
const URLState = ({ initial = false, character='cat', mood='blissful', colour='green', showDetails='false', detail='code' } = {}) => {
const characterLink = `/character/${mood}/${sentenceCaseToKebapCase(colour)}/${pascalCaseToKebapCase(character)}/${showDetails ? `details/${detail}/` : ''}`
return kitten.html`
<script id='update-state'>
if (${initial ? 'false' : 'true'}) {
history.pushState({}, '', '${characterLink}')
}
</script>
`
}
Any time the state changes, we just stream this component to the client.
Yes, you can stream script tags in Kitten if you need to do something that’s only available on the client, like updating the browser’s history stack.
Given the URL state, page title, and details drawer components are streamed in response to all events, I refactored the app at some point to pull them out into their own function:
function updateMainState({ page, character, colour, mood, showDetails, detail }) {
page.send(kitten.html`<${URLState} character=${character} colour=${colour} mood=${mood} showDetails=${showDetails} detail=${detail} />`)
page.send(kitten.html`<${Title} character=${character} colour=${colour} mood=${mood} showDetails=${showDetails} detail=${detail} />`)
page.send(kitten.html`<${DetailsDrawer} character=${character} colour=${colour} mood=${mood} show=${showDetails} detail=${detail} />`)
}
When you stream a component to the client, Kitten knows which component to update based on its ID.
The page
reference is the one sent to your onConnect()
handler and the send()
method streams the updated component to the current page using a WebSocket connection for the page that’s managed by Kitten.
To learn more about Kitten’s Streaming HTML workflow, follow along with the Streaming HTML tutorial.
Responsiveness
From the outset, I wanted the experience to be as personable on a tiny viewport in the palm of your hand as it is on your laptop or desktop. So the first decision was to focus on one character at a time to enable the character to be as large as possible to really let Elizabet’s gorgeous illustrations shine.
I also wanted to avoid modals jarring you out of the experience and so I decided early on to have the details view transition in.
Easier said that done, apparently. (Thank you, CSS.)
Zero to auto height transitions using just CSS in 2024
One of the challenges was making what is essentially a 0 to auto height transition happen smoothly.
Long rumoured to be impossible using CSS alone, Nelson Menezes apparently figured out how to do this in 2021 by animating the grid-template-rows
attribute of a single row grid containing an element with min-height
of zero from 0fr
to 1fr
.
Back then, browser support was sketchy but today, it works perfectly across all evergreen browsers.
const DetailsDrawer = ({ character, colour, mood, show, detail }) => kitten.html`
<div
id=details-drawer
style="grid-template-rows: ${show ? '1fr' : '0fr' };"
morph
aria-hidden=${show ? 'false' : 'true'}
aria-live=polite
>
<${Toast} aria-hidden=true />
<${DetailTabs}
character=${character}
colour=${colour}
mood=${mood}
detail=${detail} />
</div>
`
Wondering what the morph
attribute does on the <div>
? It tells it to use Kitten’s built-in support for idiomorph to ensure that only the elements and attributes that are changed are updated in the DOM when you send an updated copy of it from the server to the client using Kitten’s Streaming HTML workflow.
Also, while I’m using lightweight function-based simple components (see tutorial) in this example, you can also make use of Kitten’s new kitten.Component
components if you’re building more complicated sites/apps with layout components and hierarchies of components that all handle events in a well-encapsulated manner.
You can see an example of the latter components in the Profile section code of Place.
Of course, any time you’re revealing new content it’s important to ensure that you set the aria-live
attribute of the parent area to polite
so that people who use screen readers and other assistive technologies can be notified of the change.
One little aspect that makes the experience feel more like an app than a site is that the initial interface takes up the whole page.
On viewports where the entire height of the interface can fit while displaying the details view, this remains the case. On viewports where there isn’t enough vertical space to fit everything, you can scroll to see the overflowing content once the details view appears.
Local running instructions
As an addition to the original, I added a tab-bar interface that contains instructions on how to create an app that uses the character you configured locally.
Given how lightweight the process of creating and working with Kitten apps is, this was easy to fit into the interface without overwhelming it.
For example, if you wanted to create a web page with a cute minimalist vector illustration of a person wearing cotton-candy coloured cat kigurumi pyjamas looking lovestruck, here’s what you’d do:
-
In terminal, create a folder for your project, enter it, initialise your app as a node module using Kitten’s version of
npm
, install the Kitten Kawaii module, and then create an empty index.page.js file:mkdir kawaii && cd kawaii kitten-npm init -y kitten-npm install kitten-kawaii touch index.page.js
-
Add the following code to your index.page.js file:
import { CatKigurumi } from 'kitten-kawaii' export default () => kitten.html` <${CatKigurumi} mood='lovestruck' colour='#ffbae1' /> `
-
Run
kitten --open
to view the character in your browser.
Downloadable SVGs
Just like React Kawaii shows you the code you need to add the configurable component to your React act, Kitten Kawaii shows you the code (and instructions) to add it to your Kitten app or site.
But what if you don’t want to use Kitten?
Or what if you don’t need the character to be configurable in your app or site?
In other words, what if you just want the resulting SVG?
Easy: just use the download button to save it locally.
One of the design principles I adopted in the design of Kitten is that any magic Kitten adds should be a progressive enhancement on existing technologies and standards so that you are not locked into Kitten in any way.
The same principle explains why I’m making Domain instead of just having our own hosting service up and running on small-web.org (something that would have happened by now if I didn’t care about having every aspect of the Small Web be free and open and without lock-in.)
Accessible SVGs
To make the SVGs accessible, I included role='image'
as well as dynamically-populated aria-label
and <title>
and <desc>
tags.
Here’s an example from a configured character SVG:
<svg
role="img"
aria-label="Dinosaur kigurumi character: Cute minimalist vector illustration of person wearing granny-smith-apple coloured dinosaur kigurumi pyjamas, looking lovestruck."
…
>
<title>Dinosaur kigurumi character</title>
<desc>Cute minimalist vector illustration of person wearing granny-smith-apple coloured dinosaur kigurumi pyjamas, looking lovestruck.</desc>
…
</svg>
Regardless of what CSS colour you pass to the SVGs component, you will get the nearest colour in the colour dictionary in the description thanks to the Name That Color And Hue with I18n module.
This is the only dependency used by the library.
Mood slider
I wanted the mood selection to be a slider to encourage play. It’s fun to slide your finger across the interface and see the faces changing in an animated fashion.
In order to make this accessible, I had to use the aria-valuetext
attribute and update the Mood range input on both the input
and change
events as, otherwise, VoiceOver on Mac would not read the updated value as it was being changed using the screen reader controls.
const MoodInput = ({ selectedMood }) => kitten.html`
<input
id=mood
name=mood
type=range
list=moods-list
min=0
max=6
value=${Moods.list.indexOf(selectedMood)}
aria-valuetext=${selectedMood}
connect
trigger='input, change'
morph
>
`
The connect
attribute signals to Kitten that this component triggers events that should be handled on the server.
export function onConnect ({ page }) {
// …
page.on('mood', (/** @type {{ mood: number }} */ data) => {
const mood = Moods.list[data.mood]
page.data.mood = mood
const character = page.data.character
const colour = page.data.colour
const showDetails = page.data.showDetails
const detail = page.data.detail
// We send the MoodInput back so the aria-valuetext can be
// updated. This makes screen readers read the emotions
// instead of the numeric values of the slider.
page.send(kitten.html`<${MoodInput} selectedMood=${mood} />`)
page.send(kitten.html`<${TheCharacter} character=${character} colour=${colour} mood=${mood}/>`)
page.send(kitten.html`<${SelectedMood} mood=${mood} />`)
updateMainState({ page, character, colour, mood, showDetails, detail })
})
}
Our page’s onConnect()
handler gets called automatically by Kitten when an instance of our page loads in someone’s browser. It’s there that we set up a listener for the mood
event.
The name of the event is the based on the name
attribute of the component that triggers it.
Kitten’s Streaming HTML workflow, which is a hypermedia-based worflow that’s itself built on htmx and htmx WebSocket, maps events triggered on the client to events on the server.
In the code, you can also see me setting properties on the page.data
object. This is a new feature I added to Kitten while creating this example as I needed a place to store page-scoped state (as opposed to session-scoped or persisted state) that I could access from both the default page render function as well as the onConnect()
handler and the event handlers defined there in order to create the URL-based state functionality that we’ll explore next.
Also, given Safari will not visually display a datalist
component no matter what styles you apply to it (while Chromium and Gecko-based browsers do), I had to render the labels as a separate div
, so there’s a bit of redundancy there but nothing major.
On narrower viewports, the slider labels are hidden and the main slider label displays the selected mood.
Toasts
When you press one of the Copy buttons in the interface, you get a localised little toast confirming that the text was copied to your clipboard.
Using Kitten’s Streaming HTML workflow, this was very easy to do:
Here’s the component:
const Toast = ({SLOT}) => kitten.html`
<div
id='toast'
morph='settle:2s'
aria-live='assertive'
aria-hidden='false'
>
${SLOT}
</div>
`
Initially, we render the Toast component without any content:
<${Toast} />
And then, when we want it to display, we simply send it with content in it:
page.on('copy', () => {
page.send(kitten.html`<${Toast}>Copied!</>`)
})
The toast-like behaviour is controlled via CSS and the settle:2s
value passed to the morph
attribute, which adds a settling
class to the Toast div
for two seconds after the new content is morphed into the DOM.
#toast.settling { opacity: 1; top: 4.5em; }
#toast {
position: absolute;
opacity: 0;
top: 0;
text-align: center;
right: 1em;
transition: all 0.3s ease-out;
border-radius: 0.5em;
background-color: deeppink;
color: white;
padding: 0.5em;
font-weight: bold;
z-index: 1;
}
Licensing
The original React Kawaii code and is released under the ‘liberal’ MIT License. This means, basically, that anyone can take it and use if for whatever purpose.
The reason Big Tech loves ‘liberal’ licenses is because it allows corporations to take the work and include it in their products without having to share back. It’s open source in the ‘open as in open for business’ sense of the word.
So if Google, for example, wanted to take Elizabet’s characters and train their proprietary AI on them, they could. They could even invest millions of dollars in creating a new product (“Google Kawaii”) based on her components and new ones their AI based on them and they wouldn’t have to release the source code. They wouldn’t even have to credit the original author unless they redistributed the source code (which they don’t have to do).
So when you think of ‘liberal’ licenses, think ‘liberal as in neoliberal’.
On the Small Web, we use the AGPL License which, unlike neoliberal licenses that enable corporations to take without giving back, says that if you use this code, you must share your changes to it. In other words, if you take from the commons, you must give back to it. You must ‘share alike’.
Which, when you think of it, is only fair and as it should be.
What it doesn’t let you do is to privatise the commons. And the neoliberals (and more recently, the just openly Trump-backing fascists) in Big Tech love to privatise things. They love to put fences around the commons and call it theirs. So they don’t like the AGPL license and licenses like it.
That’s ok.
We similarly don’t like that they’re destroying our habitat with their proof-of-waste ponzi schemes and water guzzling large-scale bullshit generators before they fuck off to their undergruond bunkers in New Zealand, or Mars, or wherever.
So you can say we both have our grievances.
💡 One thing to note is the relationship between copyright and licensing. You automatically have copyright in work you create. That copyright is what gives you the right to license your work.
I cannot license work that is copyright by someone else.
What I can do is license my own work and any modifications I make to existing work.
So the AGPL license in Kitten Kawaii applies to the combined work only and any original work I created (like the Kitten Kawaii web site/app, which does not share any code with React Kawaii).
Any unmodified code from React Kawaii remains under the original MIT License as licensed by the original author and copyright owner.
Comparison tables
Quantitative
The source code statistics are generated via scc from the root directory of each project after a fresh clone of its source code. (And, for React Kawaii, after deleting its dist/ folder.)
Remember as you look at these that numbers only tell part of the story.
But there is something to be said about overall complexity, weight, and experience when you see 5×-9× differences in size, a 15×-148× differences in time, 6× difference in lines of code, and a 3× difference in code complexity.
React Kawaii | Kitten Kawaii | |
---|---|---|
npm package size (unpackaged) | 615 kb | 63.1 kb |
npm package number of files | 36 | 25 |
# dependencies (library) | 17 (dev) | 1 (run-time)6 |
# dependencies (web site) | 35 (17 run-time, 18 dev) | 1 (run-time)7 |
Initial package install time (a) | 18 seconds | 1.2 seconds |
Dev server startup time (b) | 1.6 seconds | ~ 800 ms |
Initial index page compile time (c) | 16.3 seconds | 110 ms |
Time from cloning source code repository to seeing index page in dev server (a + b + c) | 35.9 seconds | 2.3 seconds |
Subsequent index page render time | ~ 45 ms | ~ 31 ms |
Number of files | 88 | 39 |
Lines of code (exc. blank lines and comments | 15, 873 | 2,264 |
Code complexity | 73 | 26 |
Source code size | 0.597 MB | 0.095 MB |
Qualitative
React Kawaii | Kitten Kawaii | |
---|---|---|
Prerequisites | Must have Node.js installed | Must have Kitten installed |
Source code repository | Github | Codeberg |
License | MIT | AGPL version 3 |
Languages used | JSX, CSS, TypeScript | HTML, CSS, JavaScript |
Type safety | ✅ Via TypeScript | ✅ Via JSDoc + TypeScript LSP at author-time |
Interface framework | Radix UI | HTML and CSS (with base styles and dark mode support provided by Kitten’s built-in semantic CSS Water library.) |
State management / Reactivity / interface updates | Client side (React / useState) | Server side (Streaming HTML) |
Build/bundler | Turbo | Kitten (no build step; pages are lazily built by Kitten first time they’re served). |
Web site framework | Next | Kitten |
Deployed using | Vercel | Domain |
Deployed on | Vercel | small-web.org |
-
Specifically, 17 development-time dependencies for the library and 17 development-time and 18 run-time packages for a total of 35 for the web site.
In contrast, Kitten Kawaii, the library, has one dependency to aid in accessibility and the web site’s only dependency is the Kitten Kawaii library. ↩︎
-
Here’s a summary of the feature parity between Kitten Kawaii and React Kawaii, including notes on where they differ slightly.
Common:
-
16 cute minimalist vector illustrations by Elizabet Oliveira that you can customise using an online interface to affect their colour and mood.
-
Live code listing for adding the configured character to your app.
-
Brief documentation of the character SVG API.
-
Dark mode support.
Unique to React Kawaii:
- Ability to adjust the size of the characters also within lower and upper limits. (Kitten Kawaii doesn’t include this as the character is displayed responsively in the interface.)
Unique to Kitten Kawaii
-
Accessible SVGs that describe the configured SVG. e.g., “Dinosaur Kigurumi character: Cute minimalist vector illustration of a person wearing cotton-candy coloured dinosaur kigurumi pyjamas looking lovestruck.”
-
Plain SVG download feature. (So you’re not locked in to using your configured character in a Kitten app; you can use it anywhere.)
-
In addition to the Code listing and property documentation, instructions on how to go from zero to having the character running in Kitten locally.
-
-
You can experience this for yourself in the video at the start of this section where we run the following compound command in bash to get up and running with React Kawaii locally:
↩︎# The two-second sleep is to give the Next.js server time to start up. This takes around 1,600-1,900ms on my machine. git clone https://github.com/miukimiu/react-kawaii.git; cd react-kawaii; pnpm i; pnpm run dev & sleep 2; $(which firefox) http://localhost:3000;
-
The downside, of course, is that the more you deviate from the standard features of built-in HTML components, the more work you have to do, especially to ensure that the components you create yourself are accessible, etc.
That said, there’s no guarantee that components in popular libraries are accessible either (or any easier to customise, should you need to change their behaviour beyond what was planned for by the component authors).
Credit where credit is due, Radix UI, the library used in React Kawaii, is quite exemplary in making accessibility a core requirement and having accessibility sections on each component page. Of course, there’s nothing stopping you from porting a library like Radix UI to Kitten hint, hint! and I’m already thinking of collecting some of the components I’ve begun using in multiple projects under a simple component library. ↩︎
-
On my new StarLabs StarLite Mk V tablet, I’ve actually rebased to Universal Blue’s Fedora Main Base Image. This doesn’t solve the issue with the broken screen reader, of course, but it sure is nice to be able to watch videos in Firefox for a change. (Although my main browser these days is Vivaldi. For reasons.) ↩︎
-
Uses the Name That Color And Hue with i18n (ntc-hi-js) library to provide colour names in the accessible text summaries of the SVGs. ↩︎
-
The Kitten Kawaii library itself is the only dependency. ↩︎