You no longer need JavaScript

Mike's Notes

I discovered this blog article by Estonian Lyra Rebane via Ray Camden. The original article has much more to play with.

Thanks, Ray, for spotting.

Ray wrote

"I feel like this is something that's been shared before, and heck, I've talked about this myself many times as well, but it's a useful reminder that many things we've used JavaScript for in the past are not actually necessary and can be done by simpler, less complex means. Check out "You no longer need JavaScript", a great post focused on CSS improvements primarily but also some relevant HTML features you may not be aware of."

Resources

References

  • Reference

Repository

  • Home > Ajabbi Research > Library > Subscriptions > Ray Camden Blog
  • Home > Handbook > 

Last Updated

21/10/2025

You no longer need JavaScript

By: Lyra Rebane
Lyra Horse Blog: 28/08/2025

hi! i’m lyra and this is my epic blog where i post epic articles about infosec, programming, and other cool stuff! There’s also stuff on my home page.

So much of the web these days is ruined by the bloat that is modern JavaScript frameworks. React apps that take several seconds to load. NextJS sites that throw random hydration errors. The node_modules folder that takes up gigabytes on your hard drive.

It’s awful. And you don’t need it.

Name Status Type Size Time
app 200 document 153.8 kB 51 ms
6920616d20612066-s.p.6f6e7421.woff2 200 font 31.5 kB 32 ms
686579206d652074-s.p.6f6f2121.woff2 200 font 28.5 kB 116 ms
77687920646f6573.css 200 stylesheet 253 kB 47 ms
2074686520646566.js 200 script 648 kB 83 ms
61756c74206e6578.js 200 script 166 kB 363 ms
746a732074616b65.js 200 script 83.3 kB 46 ms
turbopack-20757020302e354d.js 200 script 38.0 kB 95 ms
423f207468617427.js 200 script 414 B 34 ms
73206d6f72652074.js 200 script 32.6 kB 49 ms
68616e206d792065.js 200 script 15.1 kB 71 ms
6e7469726520626c.js 200 script 143 kB 48 ms
6f6721 hey there! 200 script 4.1 kB 103 ms

The intro paragraph of this post is tongue-in-cheek. It’s there to get you to read the rest of the post. I suspect the megabytes of tracking scripts intertwined with bad code is far more likely to be the real culprit behind all the terrible sites out there. Web frameworks have their time and place. And despite my personal distaste for them, I know they are used by many teams to build awesome well-optimized apps.

Despite that, I think there’s some beauty in leaving it all behind. Not just the frameworks, but JavaScript altogether. Not every site needs JavaScript. Perhaps your e-commerce site needs it for its complex carts and data visualization dashboards, but is it really a necessity for most of what’s out there?

It’s actually pretty incredible what HTML and CSS alone can achieve.

So, what do you say?

My goal with this article is to share my perspectives on the web, as well as introduce many aspects of modern HTML/CSS you may not be familiar with. I’m not trying to make you give up JavaScript, I’m just trying to show you everything that’s possible, leaving it up to you to pick what works best for whatever you’re working on.

I think there’s a lot most web developers don’t know about CSS.

And I think JS is often used where better alternatives exist.

So, let me show you what’s out there.

“But CSS sucks”

I believe a lot of the negativity towards CSS stems from not really knowing how to use it. Many developers kind of just skip learning the CSS fundamentals in favor of the more interesting Java- and TypeScript, and then go on to complain about a styling language they don’t understand.

I suspect this is due to many treating CSS as this silly third wheel for adding borders and box-shadows to a webapp. It’s undervalued and often compared to glorified crayons, rather than what it really is - a powerful domain-specific programming language.

It’s telling when to this day the only CSS joke in the webdev circles is centering a div.

i am a div
body {
  display: flex;
flex-direction: row
flex-wrap: nowrap
align-content: normal
justify-content: center
align-items: center
  justify-content: center;
  align-items: center;
}

Yes, the syntax isn’t the prettiest, but is it really that hard?

Besides, your devtools probably [1] come with a fun little gadget that lets you fiddle with the flexbox by just clicking around. You don’t even need to remember the syntax.

I don’t think CSS is fundamentally any more difficult than JS, but if you skip the basics on one and only focus on the other, it’s no surprise it feels that way.

“But it’s painful to write”

Another source of disdain for CSS is how awful it has been to write in the past. This is very much true, and is probably why things like Sass and Tailwind [2] exist.

But that’s the thing, it used to be bad.

btw u should write css like

cool-thing {
    display: flex;
    &[shadow] {
        box-shadow: 1px 1px #0007;
    }
    @media (width < 480px) {
        flex-direction: column;
    }
}

and html like

<cool-thing shadow>wow</cool-thing>

because it's allowed & modern & neat!

(yes! the code above is standards compliant [3])

In the past few years, CSS has received a ton of awesome quality-of-life additions, making it nice to do stuff that has historically required preprocessors or JavaScript.

Nesting is definitely one of my favorite additions!

In the past, you’ve had to write code that looks like this:

:root {
  --like-color: #24A4F3;
  --like-color-hover: #54B8F5;
  --like-color-active: #0A6BA8;
}
.post {
  display: block;
  background: #EEE;
  color: #111;
}
.post .avatar {
  width: 48px;
  height: 48px;
}
.post > .buttons {
  display: flex;
}
.post > .buttons .label {
  font-size: 24px;
  padding: 8px;
}
.post > .buttons .like {
  cursor: pointer;
  color: var(--like-color);
}
.post > .buttons .like:hover {
  color: var(--like-color-hover);
}
.post > .buttons .like:active {
  color: var(--like-color-active);
}
@media screen (max-width: 800px) {
  .post > .buttons .label {
    font-size: 16px;
    padding: 4px;
  }
}
@media (prefers-color-scheme: dark) {
  .post {
    background: #222;
    color: #FFF;
  }
}

And yeah, that’s pretty awful to work with. For anything that involves multiple chained selectors, you kind of have to keep a mental map of how every parent selector relates to its children, and the more CSS you add the harder it gets.

But let’s try it with nesting:

:root {
  --like-color: #24A4F3;
  --like-color-hover: hsl(from var(--like-color) h s calc(l + 10));
  --like-color-active: hsl(from var(--like-color) h s calc(l - 20));
}
.post {
  display: block;
  background: #EEE;
  color: #111;
  @media (prefers-color-scheme: dark) {
    background: #222;
    color: #FFF;
  }
  .avatar {
    width: 48px;
    height: 48px;
  }
  & > .buttons {
    display: flex;
    .label {
      font-size: 24px;
      padding: 8px;
      @media (width <= 800px) {
        font-size: 16px;
        padding: 4px;
      }
    }
    .like {
      cursor: pointer;
      color: var(--like-color);
      &:hover { color: var(--like-color-hover); }
      &:active { color: var(--like-color-active); }
    }
  }
}

That is way nicer to read [4]! All the relevant parts are right next to each other, so it’s a lot easier to understand what’s going on. Seeing the &:hover and &:active right next to the .like button is especially nice imo.

And since you can sort of see the structure - the parent selectors “guarding” the child ones - it also makes it a lot easier to get away with short and simple class names (or even referring to elements themselves).

You may have noticed that I’m also making use of relative colors in the second example. I think the MDN article has a lot of awesome examples, but the jist of it is that you can take an existing color, modify it in many different ways across multiple color spaces, and mix it with other colors using color-mix().

[IMAGE]

(yes! the color picker above is written in just css)

The source

/* remove blue from a color */
rgb(from #123456 r g 0);
/* make a color transparent */
rgb(from #123456 r g b / 0.5);
/* make a color lighter */
hsl(from #123456 h s calc(l + 10));
/* change the hue in oklch color space */
oklch(from #123456 l c calc(h + 10));
/* mix two colors in oklab color space */
color-mix(in oklab, #8CFFDB, #04593B 25%);
These snippets are really useful for when you want something to be just ever so slightly darker or brighter, such as a button hover effect or a matching border color, and they’re way nicer to use than doing all those color conversions in JavaScript. If you’re feeling particularly adventurous, you could even go ahead and generate your entire color scheme in just CSS.
/* This is editable ^_^ */
@property --cqw {
  syntax: '<length>';
  inherits: true;
  initial-value: 1cqw;
}
@property --cqh {
  syntax: '<length>';
  inherits: true;
  initial-value: 1cqh;
}
@property --vw {
  syntax: '<length>';
  inherits: true;
  initial-value: 100vw;
}
@property --vh {
  syntax: '<length>';
  inherits: true;
  initial-value: 100vh;
}
@property --svh {
  syntax: '<length>';
  inherits: true;
  initial-value: 100svh;
}
@property --dvh {
  syntax: '<length>';
  inherits: true;
  initial-value: 100dvh;
}
@property --lvh {
  syntax: '<length>';
  inherits: true;
  initial-value: 100lvh;
}
/* oklch easter egg */
color-demo:has(color-style:active):not(:has(color-swatch:active)) {
  color-bg {
    background-image: linear-gradient(in oklch to right,
      oklch(75% 100% 0),
      oklch(75% 100% 120deg),
      oklch(75% 100% 240deg),
      oklch(75% 100% 360deg)
    ), linear-gradient(#000, #FFF);
  }
  color-result>div {
    --picked-color: oklch(calc(tan(atan2(var(--cqh), 1px)) / calc(var(--h) / 200)) 100% calc(tan(atan2(var(--cqw), 1px)) / calc(var(--w) / 720)));
  }
}
color-demo, color-picker, color-point, color-result, color-bg {
  display: block;
}
color-demo {
  display: grid;
  grid-template-rows: 1fr;
  grid-template-columns: 1fr;
  container-type: size;
  /*--w: 720;*/
  /*--h: 400;*/
  --w: min(calc(tan(atan2(var(--vw), 1px)) - 48), 768);
  --h: 300;
  width: calc(var(--w) * 1px);
  height:calc(var(--h) * 1px + 324px);
  border-radius: 6px;
  background: #FFF;
  overflow: clip;
  /*
  background:  linear-gradient(in hsl to right,
    hsl(0 100% 50%),
    hsl(120deg 100% 50%),
    hsl(240deg 100% 50%),
    hsl(360deg 100% 50%)
  ), linear-gradient(#000, #FFF);
  background-size: calc(100% - 16px) calc(100% - 16px);
  background-position: center;
  background-repeat: no-repeat;
  background-blend-mode: overlay;
  */
  position: relative;
  color-bg {
    color-scheme: only light;
    grid-area: 1 / 1;
    width: calc(100% - 16px);
    height: calc(var(--h) * 1px - 16px);
    margin: 8px;
    border-radius: 4px;
    background:  linear-gradient(in hsl to right,
      hsl(0 100% 50%),
      hsl(120deg 100% 50%),
      hsl(240deg 100% 50%),
      hsl(360deg 100% 50%)
    ), linear-gradient(#000, #FFF);
    background-position: center;
    background-repeat: no-repeat;
    background-blend-mode: overlay;
    box-shadow: 1px 1px 5px 0 #0002;
  }
  color-picker {
    color-scheme: only light;
    display: grid;
    grid-area: 1 / 1;
    position: relative;
    grid-template-columns: 1fr;
    grid-template-rows: 1fr;
    width:fit-content;
    height:fit-content;
    &>*{
      grid-column: 1; grid-row: 1;
    }
    color-indicator {
      --sw1: 0;
      --sw2: 0;
      --sw3: 0;
      --sw4: 0;
      --sw5: 0;
      --sw6: 0;
      --sw7: 0;
      --sw8: 0;
      --sw9: 0;
    }
    &:has(>color-result color-swatch > :nth-child(1):hover) color-indicator { --sw1: 2px }
    &:has(>color-result color-swatch > :nth-child(2):hover) color-indicator { --sw2: 2px }
    &:has(>color-result color-swatch > :nth-child(3):hover) color-indicator { --sw3: 2px }
    &:has(>color-result color-swatch > :nth-child(4):hover) color-indicator { --sw4: 2px }
    &:has(>color-result color-swatch > :nth-child(5):hover) color-indicator { --sw5: 2px }
    &:has(>color-result color-swatch > :nth-child(6):hover) color-indicator { --sw6: 2px }
    &:has(>color-result color-swatch > :nth-child(7):hover) color-indicator { --sw7: 2px }
    &:has(>color-result color-swatch > :nth-child(8):hover) color-indicator { --sw8: 2px }
    &:has(>color-result color-swatch > :nth-child(9):hover) color-indicator { --sw9: 2px }
    &:has(>color-result color-swatch[analogous]:hover) color-indicator {
      /*filter: drop-shadow(37px 0px 0px #EEE) drop-shadow(0px 20px 0px #EEE);*/
      box-shadow: 0 0 1px 1px #0005 inset, 0 0 0px 1px #0008 inset,
                  0 0 0 var(--sw3) #000,
                  calc(var(--w) * -.111px) 0px 0px var(--sw1) #A4F9,
                  calc(var(--w) * -.055px) 0px 0px var(--sw2) #C2F9,
                  calc(var(--w) * 0.055px) 0px 0px var(--sw4) #F2C9,
                  calc(var(--w) * 0.111px) 0px 0px var(--sw5) #F4A9;
    }
    &:has(>color-result color-swatch[primary]:hover) color-indicator {
      box-shadow: 0 0 1px 1px #0005 inset, 0 0 0px 1px #0008 inset,
                  0 0 0 var(--sw2) #000,
                  calc(var(--w) * -.5px) 0px 0px var(--sw1) #3039,
                  0 0 0 #0000,
                  0 0 0 #0000,
                  calc(var(--w) * 0.5px) 0px 0px var(--sw3) #3039;
    }
    &:has(>color-result color-swatch[monochrome]:hover) color-indicator {
      bottom: calc(100% - 14px - var(--h) * 0.1px);
      outline: 1px solid #0000;
      box-shadow: 0 0 1px 1px #0005 inset, 0 0 0px 1px #0008 inset,
                  0 0 0 var(--sw1) #000,
                  0 calc(var(--h) * 0.1px) 0 var(--sw2) #F6F9,
                  0 calc(var(--h) * 0.2px) 0 var(--sw3) #E5E9,
                  0 calc(var(--h) * 0.3px) 0 var(--sw4) #C4C9,
                  0 calc(var(--h) * 0.4px) 0 var(--sw5) #A3A9,
                  0 calc(var(--h) * 0.5px) 0 var(--sw6) #8289,
                  0 calc(var(--h) * 0.6px) 0 var(--sw7) #4149,
                  0 calc(var(--h) * 0.7px) 0 var(--sw8) #2029,
                  0 calc(var(--h) * 0.8px) 0 var(--sw9) #0009;
    }
    &:has(>color-result color-swatch[success]:hover) color-indicator {
      bottom: calc(100% - 14px - var(--h) * 0.48px);
      right: calc(100% - 14px - var(--w) * 0.0166px);
      outline: 1px solid #0000;
      box-shadow: 0 0 1px 1px #0005 inset, 0 0 0px 1px #0008 inset,
                  0 0 0 var(--sw1) #000,
                  calc(var(--w) * 0.117px) 0 0 var(--sw2) #5529,
                  calc(var(--w) * 0.372px) 0 0 var(--sw3) #2A29,
                  calc(var(--w) * 0.544px) 0 0 var(--sw4) #2289,
                  calc(var(--w) * 0.711px) 0 0 var(--sw5) #0000;
    }
  }
  color-point {
    width: 16px;
    height: 16px;
    background: #0003;
    resize: both;
    overflow: auto;
    max-width: 100cqw;
    max-height: calc(var(--h) * 1px);
    opacity: 0;
    clip-path: polygon(calc(100% - 16px) calc(100% - 16px), calc(100% - 16px) 100%, 100% 100%, 100% calc(100% - 16px));
    /* for mobile support */
    touch-action: none;
  }
  color-indicator {
    display: block;
    position: absolute;
    width: 8px;
    height: 8px;
    border-radius: 16px;
    pointer-events: none;
    border: 2px solid #FFF;
    outline: 1px solid #000;
    box-shadow: 0 0 1px 1px #0005 inset, 0 0 0px 1px #0008 inset;
    transition: box-shadow 0.5s, bottom 0.5s, right 0.5s, outline 0.5s;
    right: 2px;
    bottom: 2px;
    /* make it easier to grab the thing on mobile */
    @media (pointer: coarse) {
      right: -2px;
      bottom: -2px;
    }
  }
  color-result {
    container-type: size;
    width: 100%;
    pointer-events: none;
    &>div {
      --cqw: calc(50cqw);
      --cqh: calc(50cqh);
      --picked-color: hsl(calc(tan(atan2(var(--cqw), 1px)) / calc(var(--w) / 720)) 100% calc(tan(atan2(var(--cqh), 1px)) / calc(var(--h) / 200)));
      --picked-color-hue: calc(tan(atan2(var(--cqw), 1px)) / calc(var(--w) / 720));
      display: flex;
      position: absolute;
      width: calc(var(--w) * 1px);
      height: 400px;
      top: calc(var(--h) * 1px);
    }
  }
  color-style {
    color-scheme: initial;
    /*--background: lch(from var(--picked-color) calc((1 - round(l / 128)) * 255) c h);*/
    --primary: var(--picked-color);
    --primary-contrast: lch(from var(--primary) calc((1 - round(l / 128)) * 255) 0 0);
    --secondary: lch(from var(--primary) l calc(min(255 - c * 10,148 - 100*round(l/100))) var(--picked-color-hue));
    --secondary-contrast: lch(from var(--secondary) calc((1 - round(l / 128)) * 255) 0 0);
    --complimentary: lch(from var(--primary) l c calc(h + 180));
    --complimentary-contrast: lch(from var(--complimentary) calc((1 - round(l / 128)) * 255) 0 0);
    --analogous-a: hsl(from var(--primary) calc(h - 40) s l);
    --analogous-b: hsl(from var(--primary) calc(h - 20) s l);
    --analogous-c: hsl(from var(--primary) calc(h + 20) s l);
    --analogous-d: hsl(from var(--primary) calc(h + 40) s l);
    --success: hsl(from var(--primary) 140deg 70 50%);
    --danger: hsl(from var(--primary) 6deg 76 50%);
    --warning: hsl(from var(--primary) 48deg 83 50%);
    --info: hsl(from var(--primary) 202deg 70 50%);
    --monochrome-100: lch(from var(--primary) 10% c h);
    --monochrome-200: lch(from var(--primary) 20% c h);
    --monochrome-300: lch(from var(--primary) 30% c h);
    --monochrome-400: lch(from var(--primary) 40% c h);
    --monochrome-500: lch(from var(--primary) 50% c h);
    --monochrome-600: lch(from var(--primary) 60% c h);
    --monochrome-700: lch(from var(--primary) 70% c h);
    --monochrome-800: lch(from var(--primary) 80% c h);
    --monochrome-900: lch(from var(--primary) 90% c h);
    --background: lch(from var(--primary) 100% calc(c / 5) h);
    /* https://github.com/system-fonts/modern-font-stacks?tab=readme-ov-file#geometric-humanist */
    font-family: Avenir, Montserrat, Corbel, 'URW Gothic', source-sans-pro, sans-serif;
    pointer-events: all;
    border: 1px solid color-mix(in hsl, var(--primary), #4444);
    background: var(--background);
    border-radius: 4px;
    margin: 8px;
    display: block;
    height: fit-content;
    width: 100%;
    @media (width < 480px) {
      font-size: 75%;
    }
    @media (width < 360px) {
      font-size: 50%;
    }
    &:not(:has(>color-swatch>div:active)) > color-swatch > div:hover {
      padding-left: 64px;
    }
    color-swatch {
      color-scheme: only light;
      margin: 10px;
      border-radius: 4px;
      overflow: clip;
      color: #000;
      width: calc(100% - 20px);
      height: 64px;
      display: flex;
      box-shadow: 1px 1px 5px 0 #0004;
      >div {
        flex-grow: 1;
        padding: 4px;
        font-weight: 600;
        display: flex;
        justify-content: flex-end;
        align-items: flex-end;
        -webkit-user-select: none;
        user-select: none;
        transition: padding 0.4s, box-shadow 0.4s, scale 0.4s;
        cursor: grab;
        box-shadow: none;
        &:active {
          cursor: grabbing;
          /*padding-left: 32px;*/
          padding-left: 52px;
          box-shadow: 0 0 16px #0005;
          z-index: 10;
        }
        &:last-child {
          margin-right: -1px;
        }
      }
      &[success] {
        color: #FFF;
        >:nth-child(1) { background: var(--success); color: var(--success-contrast); }
        >:nth-child(2) { background: var(--danger); color: var(--danger-contrast); }
        >:nth-child(3) { background: var(--warning); color: var(--warning-contrast); }
        >:nth-child(4) { background: var(--info); color: var(--info-contrast); }
      }
      &[primary] {
        >:nth-child(1) { background: var(--primary); color: var(--primary-contrast); }
        >:nth-child(2) { background: var(--complimentary); color: var(--complimentary-contrast); }
        >:nth-child(3) { background: var(--secondary); color: var(--secondary-contrast); }
      }
      &[monochrome] {
        >:nth-child(-n+5) { color: #FFF; }
        >:nth-child(1) { background: var(--monochrome-100); }
        >:nth-child(2) { background: var(--monochrome-200); }
        >:nth-child(3) { background: var(--monochrome-300); }
        >:nth-child(4) { background: var(--monochrome-400); }
        >:nth-child(5) { background: var(--monochrome-500); }
        >:nth-child(6) { background: var(--monochrome-600); }
        >:nth-child(7) { background: var(--monochrome-700); }
        >:nth-child(8) { background: var(--monochrome-800); }
        >:nth-child(9) { background: var(--monochrome-900); }
      }
      &[analogous] {
        color: var(--primary-contrast);
        >:nth-child(1) { background: var(--analogous-a); }
        >:nth-child(2) { background: var(--analogous-b); }
        >:nth-child(3) { background: var(--primary); }
        >:nth-child(4) { background: var(--analogous-c); }
        >:nth-child(5) { background: var(--analogous-d); }
      }
    }
  }
}

There are so many cool new CSS features that make writing it just that little bit nicer. Things like letting you use (width <= 768px) instead of (max-width: 768px) in your @media query, the lh unit that matches the line-height, the scrollbar-gutter property that solves the little scrollbar-related layout shifts, or the ability to finally center stuff vertically without flex/grid.

And all of this is brought together by the cherry on top that is Baseline. It’s a guarantee that a specific feature works in every major browser [5], and it also lets you know since when - newly available features work in all the latest browsers, and widely available ones work in browsers up to 2.5 years old. Nesting, for example, has been fully supported in all browsers since December 2023, and thus will become widely available in June 2026. You can find the Baseline symbols in various places, such as the MDN docs [6].

These are just a few examples of what makes modern CSS so much nicer to write than what we had even just 5 years ago. It almost feels like comparing ES3 [7] to ECMAScript 2025 - and I wouldn’t blame your grudge if the former is what you’re used to.

Why bother?

Okay, so CSS has more quality-of-life stuff than before. Still, why would one choose to use it over something else? Doesn’t JavaScript already let us do everything just fine?

You need to disable JavaScript to run this app.

I think my reasons for using CSS fall into two main categories - because some users don’t want to use JavaScript, and because doing things in CSS can be genuinely better.

My blog, for example, focuses on infosec topics. Many security researchers (myself included) use a hardened browser configuration to protect themselves, which often means disabling JavaScript by default. I think it’s nice that they can fully experience my blog without changing their security settings or running a separate, sandboxed browser.

The same goes for privacy-conscious users, and it makes sense! As an experiment, I opened up a local Estonian news site in a web browser with JavaScript enabled. Can you guess how many js files it fetched? (answer in footnote [8]) That’s crazy! You do not want that running on your computer.

But surely, you are not one of the evil devs who loads a double-digit number of analytics scripts on your site - is there still any reason to reach for CSS?

Well, I think a lot of things are just plain nicer to make in HTML/CSS, both from the developer and end-user perspectives, be it for ease of use, accessibility, or performance.

Hover effects for your buttons? Toast animations? Input validation? All of these things just work in CSS, and you won’t have to reinvent the wheel, or throw kilobytes of someone else’s code at it. There will always be some cases where you do need that extra flexibility JavaScript often provides, but if you don’t need that, and doing it in CSS is easier, then why not save yourself the trouble?

And the performance of CSS is so much better! Every JavaScript interaction has to go through an event loop that wastes CPU cycles, eats some battery, and adds that tiny bit of stutter to everything.

Sure, in the grand scale of things it isn’t that bad, APIs like requestAnimationFrame are really good at keeping things smooth. But CSS animations run in the separate compositor thread, and aren’t affected by stutters and blocking in the event loop.

It makes quite a difference on low-end devices, but feels nice even on high-end ones. CSS animations on my 240hz monitor look amazing [9] - JS can look pretty good too, but it has that tiny bit of stutter to it that keeps it from being perfect, especially if you plan on running other heavy code at the same time.

It also means you won’t have to worry as much about optimization, as the browser takes care of a lot more of the rendering side of things, and often runs your stuff on the GPU if possible.

Pro tip! Wanna trigger animations from JS anyways? Use the modern Web Animations API to easily play the smooth CSS animations from JS.

Transitioning

Speaking of which, I think it’s time I start showing you practical examples, and a good place to start showing the styles is well, @starting-style.

In the past it has been pretty annoying to add start animations (such as fade-ins) to elements. You’ve had to either set up an entire CSS animation with a separate @keyframes block to go with it, or do a transition using JavaScript where you first add an element to the page, then wait a frame, and then add a class to the element.

.toast {
  transition: opacity 1s, translate 1s;
  opacity: 1;
  translate: 0 0;
  @starting-style {
    opacity: 0;
    translate: 0 10px;
  }
}
Success!
replay

But this has all changed thanks to the new @starting-style at-rule!

Pretty much all you have to do is set your properties as usual, add the initial transition states to @starting-style, and add those properties to a transition. It’s pretty simple and it kind of just works without having to trigger the animation in any way.

Lunalover

Another good example of where CSS shines is theming. Many sites need separate light and dark modes, and modern CSS makes dealing with that pretty easy.

:root {
  color-scheme: light dark;
  --text: light-dark(#000, #FFF);
  --bg: light-dark(#EEE, #242936);
}

By setting the color-scheme property to light dark, you are telling the browser to automatically pick the theme according to the user preference, and you can then make use of that by setting color values with the light-dark() function.

Not only does it set your own colors, but also those of the native components, such as the default buttons, form elements, and scrollbars. It kind of just makes stuff work by default, and that’s nice!

:root {
  color-scheme: light dark;
  &:has(#theme-light:checked) {
    color-scheme: light;
  }
  &:has(#theme-dark:checked) {
    color-scheme: dark;
  }
}

You can then add some way of overriding the color-scheme property to let the user pick a theme different from their system setting. Here I am using radio buttons to accomplish that.

Pro tip! CSS can’t save the theme preference, but you can still do progressive enhancement. Make the themes work CSS-only, and then add the saving/loading of preference as an optional extra in JavaScript or server-side code.

Lyres and accordions

“But those don’t look like radio buttons” I hear you cry.

Input elements such as radio buttons and checkboxes are a great foundation to build other stuff on top of - the example above consists of labels for the buttons and invisible radio buttons that can be checked for with the :checked pseudo-class.

<radio-picker aria-label="Radio buttons example" role="radiogroup">
  <label><input type="radio" name="demo" id="veni" checked>veni</label>
  <label><input type="radio" name="demo" id="vidi">vidi</label>
  <label><input type="radio" name="demo" id="vici">vici</label>
</radio-picker>
<style>
  radio-picker {
    display: flex;
    label {
      &:has(input:checked) {
        box-shadow: inset 0px 0px 8px 0px #888;
      }
      &:has(input:focus-visible) {
        outline: 2px solid #000;
      }
      box-shadow: inset 0px 0px 1.2px 0px #000;
      padding: 10px;
      cursor: pointer;
      background: #0002;
      &:hover { background: #0004; }
      &:active { background: #0006; }
    }
    input {
      /* To allow screen reader to still access these. */
      opacity: 0;
      position: absolute;
      pointer-events: none;
    }
  }
</style>

veni

vidi

vici

This is how I made the theme selector from the previous example. I’ve made the radio buttons half-visible in the demo for clarity, but with the opacity: 0 they would not actually be visible.

There’s a whole lot going on here, so let’s break it down.

<radio-picker aria-label="Radio buttons example" role="radiogroup">

We start off with the radio-picker element - I just made it up, you can use a div instead if you’d prefer. We give it an aria-label to give the group an accessible name, and the aria role of radiogroup to make it work as a group for the radio buttons.

You could also use the fieldset element instead of doing the aria roles if that’d fit your use case better.

<label><input type="radio" name="demo" id="veni" checked>veni</label>
<label><input type="radio" name="demo" id="vidi">vidi</label>
<label><input type="radio" name="demo" id="vici">vici</label>

Next, we add the radio buttons with their respective labels - usually you’d have to use the for attribute on labels to define which element they’re referring to, but since we have the input inside the label we don’t have to do that.

All the type="radio" inputs should also have a name value set to the same thing so that they are grouped together (you still need10 the radiogroup though). And then you can give them values or ids however you want.

label {
  &:has(input:checked) {
    box-shadow: inset 0px 0px 8px 0px #888;
  }
  &:has(input:focus-visible) {
    outline: 2px solid #000;
  }
  box-shadow: inset 0px 0px 1.2px 0px #000;
  padding: 10px;
  cursor: pointer;
  background: #0002;
  &:hover { background: #0004; }
  &:active { background: #0006; }
}

We then style the labels as we wish - the :hover and :active pseudo-classes can be used to make the buttons more fun to click, the :has(input:checked) selector can be used to define the style of the selected button, and the :has(input:focus-visible) selector can be used to add an outline when someone tabs over to the button.

The difference between :focus and :focus-visible is that the former shows up even if you use your mouse, while the latter only shows up when you use keyboard navigation, so it’s often visually more clean to use the latter.

input {
  opacity: 0;
  position: absolute;
  pointer-events: none;
}

And last, we make the radio button input exist while not being visible. This is a bit hacky, but it’s how you can keep this control accessible to keyboard navigation and screen readers.

And that’s how we get the cool-looking radio buttons!

<radio-tabs>
  <div tabindex=0 id="tab-veni">veni...</div>
  <div tabindex=0 id="tab-vidi">vidi...</div>
  <div tabindex=0 id="tab-vici">vici...</div>
</radio-tabs>
<style>
  body:has(#veni:not(:checked)) #tab-veni,
  body:has(#vidi:not(:checked)) #tab-vidi,
  body:has(#vici:not(:checked)) #tab-vici {
    display: none;
  }
</style>

We can now use them in the CSS however we want by just seeing if they’re :checked. Here I made tabs with separate divs for the content by using a :has selector on a parent element to find out which radio button is currently selected.

The :has selector has to be on a parent element that contains both the radio button and the target element - you can simply use html or body if you want it to work across the entire page. You should never use something like :has(…) by itself as it’ll run the selector for every element of the page, which can cause performance issues (body:has(…) is okay).

<div>
  <details name="deets">
    <summary>What's your name?</summary>
    My name is Lyra Rebane.
  </details>
  <details name="deets">
    ...
  </details>
</div>
<style>
  div {
    border: 1px solid #AAA;
    border-radius: 8px;
    /* based on the MDN example */
    summary {
      font-weight: bold;
      margin: -0.5em -0.5em 0;
      padding: 0.5em;
      cursor: pointer;
    }
    details {
      &:last-child { border: none }
      border-bottom: 1px solid #aaa;
      padding: 0.5em 0.5em 0;
      &[open] {
        padding: 0.5em;
        summary {
          border-bottom: 1px solid #aaa;
          margin-bottom: 0.5em;
        }
      }
    }
  }
</style>

Finally, before we move on, I want to give you a quick introduction to the details element. It’s great for if you want an accordion-style menu, such as for a FAQ section. The details open and close independently of each other, but you can set their name attribute to the same value to have only one open at a time.

Using them is pretty easy, put your content and a summary tag inside a details tag, and put the title inside the summary tag. The example above is a bit more convoluted for the visual flair, but all you really need is the html part of it.

The details elements are pretty stylable! You can add animations depending on the [open] state, and you can also get rid of the arrow by setting list-style: none on the summary.

Also, ctrl+f works with it, which is a big win in my book!

Validation

And lastly, I want to show you the power of input validation in HTML and CSS.

<label for="usrname">Username</label>
<input type="text" id="usrname" pattern="\w{3,16}" required>
<small>3-16 letters, only alphanum and _.</small>
<style>
 input:valid {
   border: 1px solid green;
 }
 input:invalid {
   border: 1px solid red;
 }
</style>

This is a simple example of how you can validate an input field with a regex pattern. If you set a pattern attribute like above, a form that contains the input cannot be submitted unless the field matches the pattern. If you’re submitting something like an e-mail address, a phone number, or a url, it might make sense to use the respective input types instead of writing your own regex.

Now, where CSS comes in is styling the input to show whether its value is valid. In the example above, I’m using :valid and :invalid to set a border color, but that comes with the downside of always having your input marked, even if the user hasn’t entered anything yet.

input {
  border: none;
  border-radius: 2px;
  outline: 1px solid #000;
  &:focus { outline-width: 2px; }
  &:user-valid { outline-color: green; }
  &:user-invalid { outline-color: red; }
}

An easy win here is to instead use :user-valid and :user-invalid - these pseudo-classes only become active once you’ve interacted with input field. I also made this example use an outline instead of a border, which I think looks a lot nicer.

It may sometimes even make sense to use a combination of :valid and :user-invalid.

And of course, you can use the :has selector to style other elements depending on the input too!

Password

The password must:

- be 8-16 characters

- contain at least ⅰ roman numeral

- not end with a letter

This one's just for fun ^_-!

I do want to mention that for some stuff, such as date pickers

(
dd/mm/yyyy
)

or datalists

(
pony
), 

there are built-in elements that do the job, but you may find them limited in one way or the other. If you’re making an input like that with specific requirements, you may still need to dip your feet in a bit of JavaScript.

Do not the vw/vh

This section is kind of random but I wanted to include it here because I think a lot of people are messing this one up and I want more people to know how to do this stuff right.

So CSS has vw/vh units that correspond to 1% of the viewport width and height respectively, which makes perfect sense for desktop browsers.

Where it becomes a bit more nuanced is on mobile devices. For example, mobile versions of both Firefox and Chrome will hide the URL bar when scrolling down on a page.

This causes the vw/vh units to be a bit ambigous - do they represent the entire available screen, only the area that’s visible with the URL bar, or something in between?

If it’s the first option, you might end up with buttons or links off-screen [11]! If it’s the second, you may end up with a background div that doesn’t cover the entire background.

[See Original Blog Post]

Above is a table of values your browser reports - if you're on mobile, try scrolling the blogpost up and down so that the URL bar hides and see how the numbers change.

The solution to this is to use the new responsive viewport units: lvh, svh, and dvh.

lvh stands for largest viewport height, and thus is useful for things like backgrounds that you’d want to cover the entire screen with, and wouldn’t care about getting cut off.

svh stands for smallest viewport height, and should be used for things that must always fit on the screen, such as buttons and links.

And dvh stands for dynamic viewport height - this one will update to whatever the current viewport height is. It might seem like the obvious choice, but it should not be used for elements you don’t want resizing or moving around as the user scrolls the page, as it could become quite annoying and possibly even laggy otherwise.

Of course, the respective lvw, svw, and dvw units exist too :).

Keyboard cat

By default, the viewport units do not account for the keyboard overlaying the page.

There are two ways to deal with that: the interactive-widget attribute, and the VirtualKeyboard API.

The former option is widely supported across browsers, works without JS, and goes in the meta viewport tag. It makes it so that opening the keyboard will change all of the viewport units.

<meta name="viewport" content="width=device-width, interactive-widget=resizes-content">

The latter option is currently only supported in Chromium-based browsers, and requires a single line of JavaScript to use:

navigator.virtualKeyboard.overlaysContent = true;

The advantage of the second option is that it allows you to use environment variables in CSS to get the position and size of the keyboard, which is pretty cool.

floating-button {
  margin-bottom: env(keyboard-inset-height, 0px);
}

But considering the fact that it doesn’t work cross-browser, I’d avoid it.

CSS wishlist

Alright, so this is a little different from the rest of the post, but I wanted to bring up some things that I wish were in CSS. I haven’t fully fleshed out all of them, so some definitely wouldn’t fit the spec as-is, but maybe they can inspire some other stuff at least.

They are just fun ideas, don’t take them too seriously.

Reusable blocks

I wish it was possible to put classes in other classes in CSS, so that you could write something like:

.border {
  border: 2px solid;
  border-radius: 4px;
}
.button {
  @apply border;
}
.card {
  @apply border;
}

This is something that Tailwind already has, and that makes me jealous.

Combined @media selectors

We can currently do nested @media queries, and also multiple selectors at the same time:

div {
  &.foo, &.bar {
    color: red;
    padding: 8px;
    font-size: 2em;
  }
  @media (width < 480px) {
    color: red;
    padding: 8px;
    font-size: 2em;
  }
}

But we cannot combine the two into a single selector:

div {
  @media (width < 480px), &.foo {
    color: red;
    padding: 8px;
    font-size: 2em;
  }
}

Which means if you want to do that you’ll inevitably have to repeat code or do some silly variable hacks, neither of which is ideal.

n-th child variable

For many of the CSS crimes I like to commit, I often end up writing code like:

div {
  span:nth-child(1) { --nth: 1; }
  span:nth-child(2) { --nth: 2; }
  span:nth-child(3) { --nth: 3; }
  span:nth-child(4) { --nth: 4; }
  span:nth-child(5) { --nth: 5; }
  ...
  span {
    top: calc(--nth * 24px);
    color: hsl(calc(var(--nth) * 90deg) 100 90);
  }
}

And I think it would be a lot nicer if we could instead just do:

div {
  span {
    --nth: nth-child();
    top: calc(--nth * 24px);
    color: hsl(calc(var(--nth) * 90deg) 100 90);
  }
}

n-th letter targeting

CSS has the ability to style the ::first-letter of text. It’d be cool if were was also a ::nth-letter(…) selector, similar to :nth-child. I suspect the reason this isn’t a thing is because the ::first-letter selector is a pseudo-element, which would be a bit tricky to implement with the nth-letter idea.

/* not a real feature */
p::nth-letter(2) {
  color: red;
}

Blackle suggested that combining the nth-child() variable with :nth-letter targeting would also be fun for certain effects, such as putting the value in the sin() function to create wavy text.

div {
  /* not a real feature */
  --nth: nth-child(nth-letter);
  will-change: transform;
  translate: 0 calc(sin(var(--nth) * 0.35 - var(--wave) * 3) * 5px);
  color: color-mix(in oklch, #58C8F2, #EDA4B2 calc(sin(var(--nth) * 0.5 - var(--wave)) * 50% + 50%));
}

Unit removal

I wish you could easily remove units from values, for example by dividing them.

div {
  /* Turns into:  (no unit) */
  --screen-width: calc(100vw / 1px);
  color: hsl(var(--screen-width) 100, 50);
}

This would allow you to use the size of the viewport or container as a numeric variable for things other than length. For example, the color picker from earlier uses it to convert the location of the color picker dot to a number to be used in a color value instead.

Uh, but wait? Does that mean this feature already exists?

Yeah, lol! We already have the ability to get unitless values in CSS, but it involves doing hacky stuff such as tan(atan2(var(--vw), 1px)) with a custom @property. It’d be nice to have this as just a division, for example.

Oh, and good news, this one we might actually be getting soon!

Also if you do something like calc(1px + sqrt(1px * 1px)) your browser will crash [12].

A better image function

The image() function exists, but no browsers implement it. It’s similar to just using url(), but adds some really cool features such as a fallback color, and image fragments to crop a smaller section out of a bigger image (think spritesheets).

We can already do both fallbacks and spritesheets with the various background properties, but it’d be nice to have this pretty syntax. I’d honestly love this syntax even more for <img> tags than CSS.

style tags in body

I make heavy use of <style> tags in <body> for my projects. On my blog, for example, I write the relevant CSS close to their graphics so that you can start reading the blog before the entire page (or the entire CSS) has finished loading [13]. And it works great!

But what’s unfortunate is that despite browsers supporting this, and major sites using this, it’s not officially spec-compliant. I suspect it’s in the spec to avoid the FOUC footgun, but there are so many reasons you would want/need style in body that I don’t think it justifies it.

I think an HTML validator should warn for this, but not error.

The art

I want to end this article by saying that to me, web development is an art, and thus, CSS is too. I often have a hard time relating to people who do webdev solely to earn money or build a startup - web development is very different when you’re on a team and are given tasks from above instead of having free will over what you create for fun.

It’s probably most apparent with things like AI [14], that for me take all the fun and creativity out of my work. But it also applies to build chain tooling such as linters and minifiers - the way I write my code is part of the art, and I don’t want a tool to erase that. I don’t even use an IDE15.

Among the practical reasons for sticking to CSS listed throughout this post, there’s a secret extra reason I like to do everything in CSS, and that’s expression and art. Art isn’t always practical, and using CSS isn’t either. But it’s how I like to express myself, and it’s why I do what I do.

I tried to keep this post approachable and practical for all web developers. But there is so much more to CSS that I’d like to talk about, so expect another post about the stuff that isn’t practical, and is instead just cool as fuck. I think CSS is a programming language, and I made a game to prove it.

But that’s a topic for another time.

afterword

it’s been almost a year since my last post, but i hope it’s been worth the wait ^_^

As usual, this post is a self-contained HTML file with no JavaScript, images, or other external resources - everything on the page is handwritten HTML/CSS, weighing in at around 49kB gzipped. it was really fun creating all the little interactive widgets and visuals this time around, i think i’ve improved in css a lot since the last time i posted.

This entire post turned out to be a bit of a fun mess (as did i!), it’s almost like a chaotic gradient of tone throughout, i hope it was still interesting and enjoyable to read though.

I have a few new posts in the works: in addition to the second CSS one mentioned earlier, I also have one about a new web vulnerability subclass I discovered, and one about a trans topic. i’m not sure when these posts will come out, but we’ll see! make sure to add me to your rss reader if that sounds fun.

I’ll also be giving a talk at BSides Tallinn in September! i’m hoping to also do css-related talks at the next ccc and disobey, but we’ll have to see whether i get accepted and have the travel budget for those.

Thank you so much for reading <3

If you’d like to reach out, feel free to message me on my socials or at lyra.horse [at] gmail.com.

Discuss this post on: twitter, mastodon, lobsters

  1. Chrome’s DevTools come with the cool flexbox widget. Firefox’s however don’t seem to for some reason? I find that weird because Firefox does have really good tools for flexbox and grid development, so this seems like an odd omission. ↩︎
  2. While I think what I said is true, Tailwind does have more to its existence, the core of which can be found in this post by its creator. ↩︎
  3. You are allowed to just make up elements as long as their names contain a hyphen. Apart from the 8 existing tags listed at the link, no HTML tags contain a hyphen and none ever will. The spec even has <math-α> and <emotion-😍> as examples of allowed names. You are allowed to make up attributes on an autonomous custom element, but for other elements (built-in or extended) you should only make up data-* attributes. I make heavy use of this on my blog to make writing HTML and CSS nicer and avoid meaningless div-soup. ↩︎
  4. Still not nice to read for you? I’m personally not a fan of BEM, but I’d definitely recommend reading up on it too if you just don’t vibe with the way I’m writing my examples. Also, my example intentionally shows off a lot of the syntax at once, but in the real world it might make sense to structure things a little differently. ↩︎
  5. Baseline browsers are Safari (macOS/iOS), Chrome (desktop/Android), Edge (desktop), and Firefox (desktop/Android). ↩︎
  6. The MDN docs of course also list detailed browser compatibility, but the Baseline symbols are nice for just getting a quick “yeah, we can use it and it’ll work for everyone” type overview. ↩︎
  7. ES3 (1999) is the last “classic” version of JavaScript. In 2009 we got the first major revision known as ES5, and a few years later we kicked off the yearly spec updates with ES2015. Also ES4 was abandoned which makes me feel sad :c. ↩︎
  8. 93 files!! Seems like they’re 1/3 functionality, 1/3 ads, and 1/3 analytics. The site works just fine with JavaScript disabled - only stuff like the comments section and ads won’t load. It’s no longer a laggy mess either for some reason. ↩︎
  9. I think the x3ctf challenges page looks really smooth on my computer - the marquee text animation and clicking on the challenges is buttery. And it also runs pretty well on the low-end hardware I have. Note that some browser performance recording tools can act a bit weird with CSS animations, so make sure your tools are working as expected before using them. Unrelated, but I made some other cool x3ctf web stuff too - check out the archive. ↩︎
  10. There’s a bug in Chrome that requires you to use a fieldset/radiogroup for the radio button index to work correctly in screenreaders. Eg if you have 3 radio buttons with the same name, selecting one of them should read “radio button 1 of 3”, which is what Firefox does, but in Chrome it will instead read it as “radio button 4 of 9” or whatever if you don’t have a fieldset/radiogroup because it kind of just combines all the radio buttons on the page into a single index. ↩︎
  11. A certain HR platform I have to use puts its action buttons at the very bottom of a 100vh container, leading to them not being visible/interactable on my phone - not a headache you want to go through when requesting sick days. It’s a good example of how just using the wrong unit can cause a pretty bad real world accessibility problem. ↩︎
  12. Well, probably not. This is a bug I found while writing this post that only affects Chrome, and it’ll probably get fixed before it even manages to hit stable. Update: I took so long to get this blog post out that it has been fixed now. During the writing of this blog post I found another bug in Chrome though, which is pretty funny. Update 2: I found yet another Chrome bug while writing this post, this one is kinda weird, you should read it. ↩︎
  13. This matters for people on slow connections, such as bad mobile data, satellite internet, tor, or iodine. While my blog posts are very small in size, the CSS alone can take up more than the first 14kB of a TCP round trip, so with blocking CSS in the head you might have to wait a few extra seconds (or minutes, in the case of iodine) just to start reading the first paragraph. Now, that 14kB number isn’t completely accurate in the modern world, but testing on my own server (HTTP/2, TLS 1.3), around ~16kB of the compressed html reaches the browser in the first batch of http data. ↩︎
  14. By this I mean tools such as Copilot, Cursor, chatbots etc. I understand there is a huge difference between full-on vibe coding and just using the tab key, but I do not want to use or interact with any of those tools. Please respect that. ↩︎
  15. I write all my code (and blogposts) in Sublime Text, which to me is just a glorified version of Notepad. The features over Notepad it gives me are syntax highlighting, multiple cursors, keyboard shortcuts, and a better visual design. It doesn’t do that much, and yet, it’s perfect. It’s so good I paid for it. ↩︎

It's 2025. All meows reserved.

Workspace Roadmap

Mike's Notes

This is the to-do list; figured out along the way.

Resources

References

  • Reference

Repository

  • Home > Ajabbi Research > Library >
  • Home > Handbook > 

Last Updated

20/10/2025

Workspace Roadmap

By: Mike Peters
On a Sandy Beach: 20/10/2025

Mike is the inventor and architect of Pipi and the founder of Ajabbi.

Roadmap

This is a list of tasks subject to change. The tasks will be added to the existing Ajabbi Developer roadmap.

Step Status Task Engine
01 Done Update Workspace database with revised definitions. Workspace
02Done Draw workspace paper mockups. NA
03 Underway Create mockups of workspace static page layouts using a hidden table layout. NA
04
Create templates from static mockups. Template
05
Connect templates to the database. Workspace
06
Render the initial set of pages from the database. Render
07
Create initial workflow steps. Workfow
08
Render pages from workflow steps. Workflow
09
Add Workflow Breadcrumb. CMS
10




Replace layout tables with CSS. Design System
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       

Turning Point

Being able to create, edit and delete (CRUD) workspaces using a dedicated workspace editor will be a turning point. Workspaces that interact with Pipi engines will significantly accelerate the development of Pipi 9. They could be navigated from pipiWiki pages.

Immediately useful tools

  • Industry Workspace Editor
  • Pipi Workspace Editor
  • Workflow Editor
  • Form Editor
  • Design System Component Editor
  • DevOps Editor
  • pipiWiki Editor
  • ...

Word Order 101

Mike's Notes

Something I need to learn for UI i18n translations.

Resources

References

  • Reference

Repository

  • Home > Ajabbi Research > Library >
  • Home > Handbook > 

Last Updated

19/10/2025

Word Order 101

By: Mike Peters
On a Sandy Beach: 19/10/2025

Mike is the inventor and architect of Pipi and the founder of Ajabbi.

My notes from Wikipedia

"In linguistics, word order (also known as linear order) is the order of the syntactic constituents of a language. Word-order typology studies it from a cross-linguistic perspective and examines how languages employ different word-order patterns. Correlations between orders found in different syntactic sub-domains are also of interest. The primary word orders that are of interest are

  • The constituent order of a clause, namely the relative order of subject, object, and verb.
  • The order of modifiers (adjectives, numerals, demonstratives, possessives, and adjuncts) in a noun phrase;
  • The order of adverbials.

...

These are all possible word orders for the subject, object, and verb in the order of most common to rarest (the examples use "she" as the subject, "loves" as the verb, and "him" as the object):

Word Order Name

  • Code
  • % of languages
  • Example languages
  • Example of usage

Subject-Object-Verb

  • SOV
  • 45%
  • Ainu, Amharic, Ancient Greek, Akkadian, Armenian, Aymara, Bambara, Basque, Bengali, Burmese, Burushaski, Chukchi, Cushitic languages, Dravidian languages, Elamite, Hindustani, Hittite, Hopi, Itelmen, Japanese, Korean, Kurdish, Latin, Lhasa Tibetan, Manchu, Mongolian, Munda languages, Navajo, Nepali, Nivkh, Northeast Caucasian languages, Northwest Caucasian languages, Pali, Pashto, Persian, Quechua, Sanskrit, Sinhala, Tigrinya, Turkic languages, Yukaghir
  • She him loves

Subject-Verb-Object

  • SVO
  • 42%
  • Arabic (modern spoken varieties), Chinese, most European languages, Hausa, Hebrew, Indonesian, Pa'O, Kashmiri, Malay, Swahili, Thai, Vietnamese
  • She loves him

Verb-Subject-Object

  • VSO
  • 9%
  • Arabic (classical and modern standard), Berber languages, Biblical Hebrew, Celtic languages, Filipino, Geʽez, Kariri, Polynesian languages
  • Loves she him

Verb-Object-Subject

  • VOS
  • 3%
  • Algonquian languages, Arawakan languages, Car, Chumash, Fijian, Malagasy, Mayan languages, Otomanguean languages, Qʼeqchiʼ, Salishan languages, Terêna
  • Loves him she

Objects-Verb-Subject

  • OVS
  • 1%
  • Äiwoo, Hixkaryana, Urarina
  • Him loves she

Object-Subject-Verb

  • OSV
  • 0%
  • Xavante. Tobati, Warao, Haida
  • Him she loves

" - Wikipedia

Celebrating the 75th Anniversary of the Turing Test

Mike's Notes

Note

Resources

References

Repository

  • Home > Ajabbi Research > Library > Authors > Alan Turing
  • Home > Ajabbi Research > Library > Subscriptions > Marcus on AI
  • Home > Handbook > 

Last Updated

18/10/2025

Celebrating the 75th Anniversary of the Turing Test

By: 
The Royal Society: 03/10/2025

The Royal Society is a Fellowship of many of the world's most eminent scientists and is the oldest scientific academy in continuous existence.

From the ACM

Published in October 1950, Alan Turing’s seminal paper “Computing Machinery and Intelligence” proposed a test to determine whether machines could think and the hope that machines would eventually compete with humans in all intellectual fields. Seventy-five years later we see this emergent competition between intelligent machines and humans played out in the news media, accompanied by international government interventions and widespread public concern.

To mark Turing's achievement, the Royal Society and the Web Science Institute, University of Southampton, UK, have organised a celebration of the 75th anniversary of the Turing Test, co-sponsored by ACM. The event is being held on October 2, 2025, 12:30 - 7 pm UTC, at The Royal Society, London, and will be hosted by former ACM President Dame Wendy Hall. Speakers will be ACM A.M Turing laureate Alan Kay, Gary Marcus, and Sir Nigel Shadbolt.

The event will be hosted by Professor Dame Wendy Hall FRS, Regius Chair of Computer Science and Director of the Web Science Institute at the University of Southampton, and Areeq Chowdhury, Head of Policy, Data and Digital Technologies at the Royal Society.  The event will feature panel discussions, an AI Exhibition and a reception.

There will be three panels addressing the topics:

    • What Did Turing Mean? And How Was It Interpreted?
      Panelists - Thomas Irvine, Sarah Dillon, Stevan Harnad, Sir Dermot Turing, and Alan Kay
    • How is the Turing Test Being Used Today and Is It Still Relevant?
      Panelists - ACM President Yannis Ioannidis, Abeba Birhane, Yarin Gal, Kaitlyn Regehr, and Gary Marcus
    • What is, or Will AGI Be? What Should the Turing Test for the Future Be?
      Panelists - Dame Wendy Hall, William Isaac, Anil Seth, Shannon Vallor, and Sir Nigel Shadbolt

Video

5:05:30

CF Scheduled Tasks: more than you may know, and should

Mike's Notes

Pipi 9 extensively uses <CFschedule>, a CFML tag for outputting files. CFSchedule does this job very well. Charlie Arehart's talks and writings are excellent.

Thank you, Charlie, for all your work.

Resources

References

  • Reference

Repository

  • Home > Ajabbi Research > Library > Authors > Charlie Arehart
  • Home > Handbook > 

Last Updated

23/10/2025

CF Scheduled Tasks: more than you may know, and should

By: Charlie Arehart
Carehart.org: 07/01/2025 (updated)

The site of server troubleshooter Charlie Arehart.

If folks were asked to discuss CF scheduled tasks, I suspect most would feel "there's not much to say". But there really is a lot more to working with them than simply "setting a given url to run on a given schedule" (did you know it could be a CFC, for instance? Did you know about the cron time feature? Did you know about clustering scheduled tasks--even with ColdFusion Standard?). Besides there being more features than many may realize, there are also some common problems people face when running them, and there are tools and techniques to help with that.

In this talk, veteran CF troubleshooter Charlie Arehart will address all these and more, starting with several ways to create them (yes, even more than just cfschedule and the CF Admin UI) then more on those more advanced/easily missed features, as well as tools and techniques for solving common problems with them. We'll also explore configuration files and features for managing tasks. Finally we'll also cover ways CF scheduled tasks may seem to NOT work and discuss alternatives to them.

Presented at Online CF Meetup Aug 8 2024, Hawaii CF Meetup (online) Jul 28 2023

Presentation Slides (PDF)

Meetup Recording on YouTube

Updates to Versioning Engine

Mike's Notes

This week's changes to the Versioning Engine (ver).

Resources

References

  • Reference

Repository

  • Home > Ajabbi Research > Library >
  • Home > Handbook > 

Last Updated

16/10/2025

Updates to Versioning Engine

By: Mike Peters
On a Sandy Beach: 16/10/2025

Mike is the inventor and architect of Pipi and the founder of Ajabbi.

The recent UI workspaces work necessitated changes to Pipi's Versioning Engine (ver). That engine's data model was changed.

Objects that can change

  • Industry models
  • Industry Schema
  • Workspace UI's
  • User training material
  • Developer Documentation
  • Etc

Versioned namespaces

Every object has a versioned namespace. When a logged change to an object occurs, the Pipi Build Number increments.

On any day, if there is one or more changes to the Build Number, the daily Patch Release automatically increases by 1. The changes are then published in the Changelog.

Minor Release

On the second Friday of every third month, the Minor Release increases by 1. A big picture summary of the preceding three months is published in the Changelog.

Objects in sync

Changes to the workspace UI, developer documentation, user training materials, and related materials need to be kept in sync.

DevOps

Making sure that the recently added DevOps Engine (dvp) runs this sync process might work. It could be done using workflows.

Summary

The engines involved are 

  • Versioning
  • DevOps
  • Learning Object
  • Workspace
  • CMS

Mixins and CFML

Mike's Notes

Pipi needs multi-inheritance, so how?

Modelling nature and complex systems requires multiple inheritance, but CFML does not support it. Here are some initial notes on figuring out what to do. I discovered some possible solutions on Ben Nadel's blog.

Thank you, Ben Nadel.

Resources

References

  • Reference

Repository

  • Home > Ajabbi Research > Library >
  • Home > Handbook > 

Last Updated

15/10/2025

Mixins and CFML

By: Mike Peters
On a Sandy Beach: 15/10/2025

Mike is the inventor and architect of Pipi and the founder of Ajabbi.

When building Pipi 4, 20 years ago, I once wrote to software architect  Sean Corfield asking how to implement multiple inheritance in CFML.

He replied, "Multiple inheritance is bad".

I'm using a database-driven workaround based on BORO at the moment, which works. The thing is, I need a better way to implement multiple inheritance in code.

This morning, for no real reason, I did a Google search for

"cfapplication multiple inheritance cfml"

And discovered what Ben Nadel came up with. He uses CFInclude to create a mixin.

From Wikipedia

"In object-oriented programming languages, a mixin (or mix-in) is a class that contains methods for use by other classes without having to be the parent class of those other classes. How those other classes gain access to the mixin's methods depends on the language. Mixins are sometimes described as being "included" rather than "inherited".

Mixins encourage code reuse and can be used to avoid the inheritance ambiguity that multiple inheritance can cause (the "diamond problem"), or to work around lack of support for multiple inheritance in a language. A mixin can also be viewed as an interface with implemented methods. This pattern is an example of enforcing the dependency inversion principle."

From Ben Nadel on 2013-06-07 

A couple of months ago, I read Practical Object-Oriented Design in Ruby: An Agile Primer, by Sandi Metz. I haven't reviewed the book yet, but I wanted to explore one of the concepts Metz talked about: Ruby Modules. From what I understood (and this may be somewhat off-base, I'm not a Ruby programmer), a module is a way to "inherit" behavior without using classical inheritance. Essentially, behavior is "included" into a class, rather than "inherited" into a class. In a language that only allows for single-class inheritance, a Module provides a mechanism for including behavior from several different sources. Furthermore, it allows the developer to borrow behaviors without worrying about the "is-a-type-of" inheritance relationship. It seems fairly interesting, so I wanted to see how this kind of behavior could be used in ColdFusion."

Code Sample

From https://www.bennadel.com/blog/1551-exploring-mixins-and-coldfusion-components.htm

<cfcomponent
output="false"
hint="I am a component whose methods are created via Mixin methodology.">

<!--- Include the mixin UDFs in the pseudo-constructor. --->
<cfinclude template="public_udf.cfm" />
<cfinclude template="private_udf.cfm" />


<cffunction
name="Init"
access="public"
returntype="any"
output="false"
hint="I return an initialized object.">

<!--- Return THIS reference. --->
<cfreturn THIS />
</cffunction>


<cffunction
name="GetVariables"
access="public"
returntype="any"
output="false"
hint="I return the private scope of this component.">

<cfreturn VARIABLES />
</cffunction>

</cfcomponent>

Ben's blog posts go into much more detail. I will need to experiment and test. Maybe a combination of the existing Boro datamodel and this code might work. It adds a lot of complexity and could be a maintenance nightmare, but with Pipi managing it, it should be OK. Pipi runs on complexity.

Summary

The CFinclude can go in the CFcomponent and/or the CFfunction.

Producing a Better Software Architecture with Residuality Theory

Mike's Notes

Very interesting. Read the references and watch the talk.

Resources

References

  • An Introduction to Residuality Theory: Software Design Heuristics for Complex Systems by Barry M. Barry O'Reilly. 2020.

Repository

  • Home > Ajabbi Research > Library > Subscriptions > InfoQ
  • Home > Handbook > 

Last Updated

14/10/2025

Producing a Better Software Architecture with Residuality Theory

By: Ben Linders
InfoQ: 07/10/2025

Ben Linders is an Independent Consultant in Agile, Lean, Quality and Continuous Improvement, based in The Netherlands. Author of Getting Value out of Agile Retrospectives, Waardevolle Agile Retrospectives, What Drives Quality, The Agile Self-assessment Game, Problem? What Problem?, and Continuous Improvement. Creator of many Agile Coaching Tools, for example, the Agile Self-assessment Game. As an adviser, coach and trainer he helps organizations by deploying effective software development and management practices. He focuses on continuous improvement, collaboration and communication, and professional development, to deliver business value to customers. Ben is an active member of networks on Agile, Lean and Quality, and a frequent speaker and writer. He shares his experience in a bilingual blog (Dutch and English) and as an editor for Agile at InfoQ. Follow him on twitter: @BenLinders.

Software architecture is tough because it blends coding, math, and business systems. Due to surprises, architectures tend to become irrelevant over time, Barry O’Reilly said at Goto Copenhagen. He presented residuality theory, where he suggested stressing naive architectures to reveal hidden "attractors" in complex business systems. This allows designs to better survive change and uncertainty.

Software architecture is difficult because it requires a broad skillset, O’Reilly said. We have to master the world of code, math, and logic, and the world of human and business systems, and understand how these two relate to and affect each other.

At university, they only teach us the tech. When we start to design systems, we realise that the complexity of the business environment constantly surprises us, rendering our architectures irrelevant, O’Reilly said. Making something as static and rigid as a software architecture survive in a fluid world that is always changing is not an easy task, he added.

Residuality assumes that a random simulation of stress on a naive architecture will produce a better architecture than the traditional methods around requirements engineering, risk management, or reacting to change as it happens, O’Reilly explained:

This started out as a curious observation, and for the last 10 years, I have had to build theoretical explanations and experiments to show that this actually is the case. Armed with this knowledge, we can think differently about software architecture and build new tools.

As students of Western science, our first recourse is to reduce any complex system to its component parts and study those parts in detail. This is the default for software engineers, O’Reilly said. In complex systems, the number of elements and potential interactions and states makes this kind of detail-oriented analysis impossible. Previous generations of architects have tried to reduce the complexity of the business environment to logic, or to structures, or to the development process.

One of the key aspects of complex systems is that they never visit all the possible states that the combination of their elements allows. Instead, the interactions of the elements constrain the system to a very small number of potential states, which we call "attractors", O’Reilly said. A complex business system is therefore not modelled as a number of interaction elements and their relationships, but instead as a number of attractors, he explained:

When we build an architecture, it is these attractors that the architecture must survive in. Attractors therefore provide a much simpler and easier and more pragmatic way of interfacing with the complexity of the environment.

The problem is that we don’t know what the attractors are, but by randomly simulating stress, we can discover many of them, O’Reilly said. If you think back to the major architectural failures you’ve seen, you will see that they mostly fail because they missed attractors, he mentioned.

Residuality theory is a very simple process. Sometimes, people are put off because the theoretical work necessary to prove that residuality works is very heavy, but applying it is easy, O’Reilly explained:

We start out with a suggestion, a naive architecture that solves the functional problem. From there we stress the architecture with potential changes in the environment. These stressors allow us to uncover the attractors, often through conversations with domain experts. For each attractor, we identify the residue, what’s left of our architecture in this attractor, and then we change the naive architecture to make it survive better.

We do this many times and, at the end, integrate all of these augmented residues into a coherent architecture. We can then test this to show that it survives unknown forms of stress better than our naive architecture.

In complex business environments with uncertainty, residuality makes it possible to create architectures quickly instead of chasing down stakeholders demanding specific requirements or answers to questions that are unknown by the business itself, O’Reilly said. It pulls technical architects out of details and teaches them to productively engage with a business environment without the lines and boxes of traditional enterprise architecture, he concluded.

InfoQ interviewed Barry O’Reilly about residuality.

InfoQ: How can we prove that the residual architecture that we created is an improvement over the naive architecture?

Barry O’Reilly: A simple test is to use a second set of stressors to check that our residual architecture survives more unknown events than our naive architecture. You can easily see the similarities between this and the training/testing sets of ML. Residuality theory ultimately states that architectures should be trained, not designed.

InfoQ: What benefits have you seen from residual analysis?

O’Reilly: Senior architects report that it gives a theoretical justification for practices that many had already figured out and a shared vocabulary for teams to talk about architecture. Ultimately, it makes architecture more explicit, better defined, and easier to teach. The result is architectures we can believe in and decision-making that is traceable.

It has its challenges as well. A small number of developers find the jump from the linear, logical, mathematical world we are trained for to the lateral, imaginative techniques very difficult. Residuality is a subject as big as OOP and requires the same amount of effort to learn.