Sign in

Jamie Kyle

@jamie.build
2.1K followers 341 following 514 posts

They/Them. Open source. Comic books. Board games. Prev: Babel, Yarn, Flow, Parcel, Biome

PostsRepliesMedia
Jamie Kyle @jamie.build · 06/08/2026
Working on a new design system component for various kinds of list items, settings rows, etc throughout our app It looks simple but it's turning out to be extremely difficult. I'm not sure it's even possible I've gotten very close except for centering "value" codepen.io/editor/Jamie...
130
Jamie Kyle @jamie.build · 21/04/2026
Omegaverse in CSS lets goooo drafts.csswg.org/css-color-5/...
::backdrop {
  /* black @ 80% opacity */
  background: alpha(from black / 80%);
}

.help {
  /* Use the current color at 50% opacity */
  color: alpha(from currentColor / 50%)
}

.emoji {
  /* Reset the opacity of emojis to 100% */
  color: alpha(from currentColor / 100%);
}
Screenshot of settings dialog showing two checkbox fields with black text on the normal one and red text on the errored one. Showing the use of the relative alpha() color syntax from the last screenshot.

1. Help text with relative color at 80% for both black and red text
2. Emoji that resets the alpha value to 100%
3. Dialog backdrop background is black at 80% opacity
080
Jamie Kyle @jamie.build · 12/02/2026
I really wish that protobufs supported creating custom scalar types
scalar AciBinary = bytes;
scalar UnixEpochMs = uint64;

message CreateMessage {
  AciBinary   author = 1;
  UnixEpochMs sentAt = 2;
  // ...
}

message DeleteMessage {
  AciBinary   targetAuthor = 1;
  UnixEpochMs targetSentAt = 2;
}

service MessageService {
  rpc Create(CreateMessage) returns ();
  rpc Delete(DeleteMessage) returns ();
}
020
Jamie Kyle @jamie.build · 11/12/2025
Using named and themed color variables in our design system has been a massive quality of life improvement Tailwind wasn't quite as bad, but even that has improved dramatically More consistency, and less you need to remember. And we've added high contrast mode, so its actually covering 4 themes
3170
Jamie Kyle @jamie.build · 13/11/2025
It sounds like you open this the same way you open a card in your wallet while your device is still locked Ideally it should *only* be accessible while your phone is unlocked, and anyone with more concern than that should already be turning their devices off entirely or not bringing them
Quote from article “You also won’t need to unlock, show, or hand over your device to present your ID — Something you shouldn’t do with driver’s licenses, either”
120
Jamie Kyle @jamie.build · 29/10/2025
This little snippet made debugging view transitions a lot easier
::view-transition-group(*) {
  animation-duration: 3000ms;
}

::view-transition-group(*)      { outline: 1px solid blue   }
::view-transition-image-pair(*) { outline: 1px solid yellow }
::view-transition-old(*)        { outline: 1px solid red    }
::view-transition-new(*)        { outline: 1px solid green  }
213516
Jamie Kyle @jamie.build · 16/10/2025
Accessibility question For ::backdrop's (dialog overlays) that have a semi-transparent bg to obscure the page behind them: Should we make the backdrop more obscured for `prefers-reduced-transparency`? Like if the backdrop is black/40% should we make it black/80%? 100%? Should we blur the bg?
dialog {
  /* The dialog itself is not semi-transparent at all */
  background: light-dark(white, black);
}

::backdrop {
  /* These values particular seem like very low values for a backdrop */
  background: light-dark(hsl(white / 20%), hsl(black / 40%));
  
  @media (prefers-reduced-transparency: reduce) {
    /* Should we increase them in reduce transparency mode? */
    background: light-dark(hsl(white / 60%), hsl(black / 80%));
    /* Would blurring the background actually help in this case */
    backdrop-filter: blur(10px);
  }
}
263
Jamie Kyle @jamie.build · 26/09/2025
GitHub allows you configure your pronouns that it shows on your profile which it also uses in some of the strings in the UI and makes accessible through the API It defaults to the value "Don't specify" but then it seems to either default to "He/Him" or tries to infer it based on your name
Screenshot of the "Pronouns" select menu with "Don't specify" selected. Other options include "they/them", "she/her", "he/him", or "Custom"Screenshot of a message from "DroidHub" (an internal company bot) with the text: Jamie marked his PR "Update symbols font" targeting main as ready for review

This text is generated based on GitHub's Webhook API which includes a value of "possessivePronoun" which is coming through as "his"

The internal bot actually tries to default to "their" if no "possessivePronoun" is returned by the webhook api
151
Jamie Kyle @jamie.build · 17/09/2025
Using backdrop-filter you can do this sort of swipe effect revealing contents behind the current element But I want this to work for the text changing its size and weight as a sort of "magnifying glass" effect I'm not sure how to accomplish that without any JS though codepen.io/jamiebuilds/...
000
Jamie Kyle @jamie.build · 09/09/2025
I'm going through our components and making fixes to forced-colors mode I haven't found much for resources on how to apply system-colors to specific components/states. Any recommendations?
Switch control in various states, rendered in forced-colors modeSelect component, opened, rendered in forced-colors modeDropdown menu component, opened, rendered in forced-colors mode
140
Jamie Kyle @jamie.build · 29/08/2025
Well then they'll LOVE my new language, brace yourself...
010
Jamie Kyle @jamie.build · 02/07/2025
Opinions needed: If I'm writing a React UI library that provides a hook that needs to attach a bunch of events and other stuff, do people prefer: - Returning a props object to pass to the right component - Accepting a ref and adding events/mutating props directly
function Example() {
  let [cardNumber, setCardNumber] = useState()
  
  let { inputProps, ...otherStuff } = useInputMask({
    mask: "0000 0000 0000 0000",
    value: cardNumber,
    onValueChange: setCardNumber,
  })
  
  // <MyInput> needs to accept all of these props:
  // 
  // inputProps = {
  //   value,
  //   onInput, onPaste, onFocus, onBlur,
  //   onKeyDown, onKeyPress, onKeyUp,
  // }
  
  return <MyInput {...inputProps}/>
}function Example() {
  let [cardNumber, setCardNumber] = useState()
  
  let ref = useRef()
  
  let { ...otherStuff } = useInputMask(ref, {
    mask: "0000 0000 0000 0000",
    value: cardNumber,
    onValueChange: setCardNumber,
  })
  
  // <MyInput> only needs to accept a ref
  
  return <MyInput ref={ref} />
}
340
Jamie Kyle @jamie.build · 01/05/2025
We found a bug in React's legacy renderer that causes a child to be re-rendered before its parent (its in an older release of React so probably not worth filing an issue) I think this is the first bug of its kind Ive found in React though, was kinda fun to hunt down gist.github.com/jamiebuilds/...
Screenshot of code in linked gist
090
Jamie Kyle @jamie.build · 25/04/2025
Lucky for us we're an Electron app and only need to support the latest version of Chrome so we can immediately make use of anchor positioning But I've been having trouble implementing this kind of containment using multiple anchors, and struggling to get it right. Can't find anyone doing this yet
Title: Anchor Positioning Containment

Section 1: In this example, the tooltip should be constrained by the scroll area of the dialog component. Its positioning should prefer the “max-height” relative to the parent scroll area and should close when its anchor is not visible [images of a scroll area inside of a dialog slowly being scrolled and the position of the tooltip flipping to use the largest available area and then disappearing when the button it is anchored to scrolls away]

Section 2: As the tooltip gets larger it should try to remain centered within the x-axis of the scroll area, and its height should fit the content as long as there is space available in the y-axis [images of the tooltip content getting longer and longer, staying within the width of the scroll area, and eventually wrapping to a new line, then overflowing with an ellipsis when there is no longer enough room for more lines]
110
Jamie Kyle @jamie.build · 19/04/2025
Wrote such good JavaScript I got to go on stage with deadmau5 at Coachella
1602
Jamie Kyle @jamie.build · 15/04/2025
Maybe this is a silly thing to want, but every time I have to slice the same string up into 3 parts I wish I could do it in one operation which would hopefully be a bit faster
let text = "aaabbbccc"
let start = 3
let end = 6

// Today: Need to call .slice() 3 times

let before = text.slice(0, start)
let inner = text.slice(start, end)
let after = text.slice(end)

// Proposal: string.slices()

let [before, inner, after] = "aaabbbccc".slices(3, 6)
//  ["aaa", "bbb", "ccc"]

// Example: Highlighting search result text

let text = "NYC flights are booked:"

let search = "flights"
let start = text.indexOf(search)
let end = start + search.length

let [before, match, after] = text.slices(start, end)
let html = `<span>${before}<em>${match}</em>${after}</span>`
// >>> "<span>NYC <em>flights</em> are booked"
130
Jamie Kyle @jamie.build · 03/04/2025
This is not at all a big deal, but why is React still using class components for error boundaries? I feel like every app has this in it by now, why not just make an official version of it
An example react component where the children are wrapped with a component titled "ErrorBoundary" with a "fallback" render prop and an "onError" pointing to window.reportError
1260
Jamie Kyle @jamie.build · 21/03/2025
I dislike that object-fit: contain is hiding that the aspect ratio of the .card-image has changed You're then limited in what styles you can add to the image. Like if you tried to add a border-radius or box-shadow to the image it wouldn't
Updated card example with the parent post's CSS changes. Showing that in the case where the card header is limiting the height of the image, now the image is still filling the container, making the box-shadow and border-radius appear outside of the visual display of the image
100
Jamie Kyle @jamie.build · 21/03/2025
Hey CSS smarties I'm not too ashamed to admit I've never been able to figure out how to solve this CSS problem even with all of the new fancy features in CSS. But I'm hopeful that someone smarter than me has been able to find a way codepen.io/thejameskyle...
Screenshot of live preview section of linked codepen, explaining the requirements, problems, and including an example of how the UI should work using an imperfect solution.Screenshot of HTML in linked codepen, showing a card component with a card header, and an image inside of the headerA screenshot of the CSS from the linked codepen, showing the card-header's CSS which currently does not specify the max-height, but ideally should. And the card-image's CSS which does specify a max-height using hardcoded values, but ideally should not.
470
Jamie Kyle @jamie.build · 14/03/2025
Neat little TypeScript trick: If you have a string or number type that you have some default/known values for: type Role = "alert" | "alertdialog" | string TypeScript will simplify the type to just: Role: string However if you wrap it like `string & {}` you'll preserve the autocompletions
7579
Jamie Kyle @jamie.build · 11/03/2025
We needed to update some legacy emoji components to use our new spritesheet It turns out it's quite a complicated problem to make these scaled relative to font-size since calc() does not play nicely mixing units So I came up with an interested hack using <svg> + <foreignObject> + <img>
1100
Jamie Kyle @jamie.build · 06/03/2025
CSS should have built-in ways of making the hitbox of an element larger than its visual space Right now apps achieve this with multiple elements or ::before/::after I would love to be able to: 1. Use containers hitbox (supporting other nested elements) 2. Expand hitbox to margins & flex/grid-gaps
2291
Jamie Kyle @jamie.build · 27/02/2025
Sure, I was about to raise an issue in CSSWG proposing something like this otherwise:
020
Jamie Kyle @jamie.build · 27/02/2025
anchor-size() doesn't seem to work in padding. But if you have ideas this is the basic UI: (It's important that the input span the entire width so that if you click on the search icon it correctly moves the cursor to the start)
200
Jamie Kyle @jamie.build · 25/02/2025
Therapy is wild because you'll have this HUGE realization about your childhood and be VERY EXCITED to tell your partner about this VERY GOOD NEWS and this will be their reaction:
0220
Jamie Kyle @jamie.build · 07/02/2025
emoji-datasource is a massive dependency (1.36 MB) so I'm building a module that serializes it in a specialized binary format that contains just the things our app needs and decodes it as a library Size: 1.36 MB → 58.4 kB That's a savings of 1.31 MB, or 95.7% smaller!
1310
Jamie Kyle @jamie.build · 02/02/2025
This feels like the news feed in Plague Inc when countries start closing their ports and you know you’re gonna lose
080
Jamie Kyle @jamie.build · 28/01/2025
LazyWeb: Is it okay if the reducer you pass to useReducer() is defined with useCallback()? It appears to work fine, but feels wrong On the other hand, maybe the stability doesn't really matter at all similar to useEffectEvent()'s design cc @danabra.mov ?
Two different useCounter() implementations using useReducer()

The first one (A) defines the reducer inside of useCounter() using useCallback() so that it can accept an amount param passed into useCounter()

The second one (B) defines the reducer outside of useCounter() so that it is always stable across re-renders, then it passes amount as an additional prop to the increment and decrement actions.

The benefit of implementation A is that the returned increment and decrement functions would be stable even as the amount param changed
281
Jamie Kyle @jamie.build · 21/01/2025
Breaking News: Everyone in the US is now a woman! Trump's executive order legally redefined everyone's sex and gender as their biological sex "at conception" ... Except all mammal fetuses start out as female, biological sex isn't determined for weeks Trump: The world's foremost gender abolitionist
Screenshot of Trump's Executive Order on 1/20/2025, in the section defining terms, with the phrase "at conception" highlighted in the definition of "female". Text reads:

(b)  “Women” or “woman” and “girls” or “girl” shall mean adult and juvenile human females, respectively.

(c)  “Men” or “man” and “boys” or “boy” shall mean adult and juvenile human males, respectively.

(d)  “Female” means a person belonging, at conception, to the sex that produces the large reproductive cell.

(e)  “Male” means a person belonging, at conception, to the sex that produces the small reproductive cell.Screenshot of the abstract section of a publication from the US National Library for Medicine. Stating:

All human individuals—whether they have an XX, an XY, or an atypical sex chromosome combination—begin development from the same starting point. During early development the gonads of the fetus remain undifferentiated; that is, all fetal genitalia are the same and are phenotypically female. After approximately 6 to 7 weeks of gestation, however, the expression of a gene on the Y chromosome induces changes that result in the development of the testes.
1232
Jamie Kyle @jamie.build · 17/01/2025
This surprised me, using the CSS relative color syntax: color(from <color> colorspace c1 c2 c3[ / A]) Taking a color in the Display P3 color space and defining a relative color in sRGB space: color(from color(display-p3 0 1 0) srgb r g b) Does not seem to force "clamp" to the new color space
080
Jamie Kyle @jamie.build · 19/12/2024
Now we’re talking
Brand “Boy Smells” with a travel spray of perfume named “Ball Drop” for $30
110
Jamie Kyle @jamie.build · 13/12/2024
This is the drawer and its dimensions. There’s more in the hamper right now but I can fit it all in here when it’s more neatly folded. I am perpetually running out of socks though
010
Jamie Kyle @jamie.build · 10/12/2024
*whispering* sometimes its okay to just have a top-level variable in your file that you use as "global state" ... you dont need to drastically restructure your code for one tiny stateful thing ... its still possible to write good tests ... it does not make you a bad developer
// This dreaded "global state" does not make you a terrible developer.
let connection = null

// Look this isn't so bad...
function db() {
  assert(connection != null, "Call connect() first")
  return connection
}

// Your database tests can know they need to call this every time
export async function connect() {
  assert(connection == null, "Do not call connect() twice")
  connection = new Database()
  await connection.connect()
}

// And this one after each test...
export function close() {
  assert(connection != null, "I cannot close() what is not opened")
  connection.close()
  connection = null
}

// Look how nice it will be to just import this function directly
export function booksList() {
  return db().query("SELECT * FROM books").all()
}

// Or this one... do you really need some DatabaseServiceFactoryController?
export function booksGet(id) {
  return db().query("SELECT * FROM books WHERE id = ?").get([id])
}

// The answer is no, feel free to be a little bit bad
export function booksAdd({ title }) {
  return db().query("INSERT INTO books (title) VALUES (?)").run([title])
}
1200
Jamie Kyle @jamie.build · 05/12/2024
I don't care much about exactly how you present instance methods in docs, but MDN presenting both these static and instance methods as `Response.json()` seems silly Response#json() Response.prototype.json() response.json() new Response().json() Response::json() Any of these would have been better
MDN docs for Response.json() - the static method for creating a response object with the provided json dataMDN docs for Response.json() - the instance method of a response for parsing the body as JSON
1120
Jamie Kyle @jamie.build · 05/12/2024
Apparently Spotify Wrapped lies to you about what your actual top songs/artists are and you have to go to other apps to get the real stats And for the most part this list makes more sense… except for the top track… did I listen to it *that* much?
410
Jamie Kyle @jamie.build · 03/12/2024
Here's a coding challenge for you: Write a mockUuid() function that returns an incrementing integer, formatted as a UUID, and make it as fast as you possibly can gist.github.com/jamiebuilds/...
Screenshot of code in linked gist
2170
Jamie Kyle @jamie.build · 26/11/2024
Ah fuck…
130
Jamie Kyle @jamie.build · 25/11/2024
I don't understand why so many people setup lint rules that limit the usage of basic JavaScript features I understand rules like banning implicit fallthroughs in switch cases, but then people will ban things like "unnecessary" block statements -- which removing could fully cause a memory leak
Code Block:

function longLivedScope() {
  	let smallerThing
	{
      let expensiveObject = new ExpensiveObject()
      smallerThing = expensiveObject.oneTimeThing()
      // expensive object now falls out of scope and can be GC'd
    }
  
  	// ...does stuff for awhile...
}
5220
Jamie Kyle @jamie.build · 20/11/2024
I’m interested to know what these numbers are now @jaz.bsky.social
020
Jamie Kyle @jamie.build · 20/11/2024
Daemon is currently stuck generating hundreds of thousands of empty log lines a minute. All this over a `make install`?
000
Jamie Kyle @jamie.build · 20/11/2024
No way only two positions are posted right now while likely hundreds of devs/designers/pms/etc are probably opening this page excited about Bluesky Keep all your job titles posted all the time, put them under "We're not actively hiring these positions, but always an open door for talented people"
Screenshot of the Bluesky jobs page with only two open positions "Feed algorithmics engineer" and "Community marketing manager"
0140
Jamie Kyle @jamie.build · 19/11/2024
This is what comes up if you go to report it. Very interested in seeing what happens when a moderator does decide to take action
000
Jamie Kyle @jamie.build · 19/11/2024
It looks like they do have a "Report List" feature which you can send to another moderator. But I'm not sure what happens then? What happens if another moderator does find that the list is malicious? And if I was subscribed to that list, will something notify me, will it disappear?
Screenshot of bluesky, on a block list, next to the "Unblock" button there is a "..." menu that has "Copy link to list" and "Report list" that opens up a dialog of moderators you subscribe to that you could report the list to
120
Jamie Kyle @jamie.build · 19/11/2024
Just an observation, not a value judgement
Code snippet: 
// When you ask people which API they want...

// This is what they will tell you is objectively better
await lib.maybeBuildAndRunWithOptionsToFinalOutput({
  appendTrailingNewlinesToAllItems: true,
  recoverFromInvalidSequences: true,
})

// This is the library that will be way more popular
await lib.build({
  newlines: true,
  loose: true,
})
1100
Jamie Kyle @jamie.build · 19/11/2024
Only semi-related, but a common difference I see between real world usage of interfaces vs type aliases Always prefer to use properties with function types, not methods Methods are bivariant even with --strictFunctionTypes www.typescriptlang.org/play/?target...
151
Jamie Kyle @jamie.build · 18/11/2024
TC39/JavaScript Idea: Utf8String() JS strings are UTF-16 but most langs/tools use UTF-8 which makes interop hard sometimes Uint8String() would actually just be a new TypedArray/ArrayBuffer view (not a new String type) It would provide familiar string APIs on raw UTF-8 bytes making interop easier
// JS strings are represented as UTF-16, but most langs/tools
// have preferred using UTF-8 for many years, which complicates interop
//
// We can never change the underlying representation of JavaScript strings, but
// still many tools need to operate on raw utf-8 bytes.
// 
// It would be nice if we could do use some familiar string APIs but operating
// on UTF-8 bytes

// The body returned by fetching example.com will return UTF-8 bytes.
// We can access those bytes directly in an ArrayBuffer.
let response = await fetch("https://example.com/")
let buffer = await response.arrayBuffer()

// You can then construct a Uint8Array from that buffer and each element
// will represent one UTF-8 code unit.
let bytes = new Uint8Array(buffer) 

// However, now you're just working with numbers
console.log(bytes)  // >> Uint8Array(1256) [60, 33, 100, 111, 99, 116, ...]

// If you want to do any string-like operations on these arrays, you basically
// need to do everything yourself

// Proposal: 
// What if we had a new TypedArray class that had string-ish methods?

let utf8 = new Utf8String(buffer)

// This is still just a Uint8Array/ArrayBuffer under the hood
utf8 instanceof Uint8Array // true
utf8.buffer === buffer // true

// Indices would still just be numbers
// (They wouldn't automatically convert to UTF-16 strings)
utf8[0] // >> 60

// But accessing malformed data would return U+FFFD (�) the "Replacement Char"
utf8[malformedCharIndex] // >> 65533

// Then we can start getting into the fun bits: Now you can have familiar
// String-like APIs that operate on UTF-8 encoded bytes rather than UTF-16
utf8.trim() // >> Utf8String
utf8.padEnd(5) // >> Utf8String

// For operations that need another string, you'd use other Utf8Strings
let other = Utf8String.from("<h1>Example Domain</h1>")
utf8.includes(other)
utf8.localeCompare(other, { sensitivity: "base" })

// Maybe even regex operations could still work
utf8.match(/Example/)
utf8.replace(/Example/, Utf8String.from("Paragon"))
6240
Jamie Kyle @jamie.build · 15/11/2024
What the hell? Why is this all I'm getting requesting my archive on Twitter?
120
Jamie Kyle @jamie.build · 15/11/2024
Or it is literally not adding browser entries for things? I've had this tab open for hours, clicked on tons of things, and somehow there's so few entries
120
Jamie Kyle @jamie.build · 15/11/2024
I'm very sad this Churro flavor appears to be discontinued, it was the only one that wasn't gross
160
Jamie Kyle @jamie.build · 15/11/2024
xkcd.com/2501/
010