Sign in

Stefan Matei

@stefanmatei.com
566 followers 216 following 388 posts

Web dev, mostly frontend, buildless and with zero dependencies when possible. Web components, modern CSS, and vanilla JS. 🇵🇸 Free Palestine 🔗 stefanmatei.com 🔗 codepen.io/nonsalant 🔗 github.com/nonsalant

PostsRepliesMedia
Stefan Matei @stefanmatei.com · 12/05/2026
The “systematic” part was the whole thesis of this report you were agreeing with
NEW REPORT DETAILS SYSTEMATIC RAPE AND SEXUAL VIOLENCE DURING HAMAS' OCT 7 ATTACK ON ISRAEL
020
Stefan Matei @stefanmatei.com · 29/03/2026
nature is healing
tweet saying:

RAM prices are plummeting after OpenAi failed to fulfill its commitment to purchase 40% of World supply and terminated its $71 billion SKHynix promise.
020
Stefan Matei @stefanmatei.com · 09/02/2026
I think there’s something wrong with the background color in dark mode
white text on light gray background
100
Stefan Matei @stefanmatei.com · 27/11/2025
When I had to make a modal/popover custom element I had it register normally, but added a few options via an attribute for how to lazy load the scripts and styles (and, optionally, the markup) inside the modal (usually other web components) github.com/nonsalant/la...
load-on (attribute)

Possible values:
• click - the modal will load everything when a trigger is clicked.
• hover (default) - the modal will load everything when a trigger is hovered or focused.
• visible - the modal will load everything when a trigger becomes visible in the viewport.
• load - the modal will load everything immediately without looking for a trigger.
020
Stefan Matei @stefanmatei.com · 07/11/2025
Great, I wanted to suggest allowing iframes when you first shared it! Do you think it would be possible to allow a zoom factor for them? 🤔 For example 0.5 would scale the *contents* of the iframe to half the normal size. For reference, the CodePen preview has something like this on mobile.
A screenshot of CodePen with an arrow pointing at a button at the bottom of the preview. The button says “0.25x”
210
Stefan Matei @stefanmatei.com · 29/10/2025
to avoid off-by-one errors, always make sure there is enough contrast between your cats
two cats sitting perpendicularly to each other on the couch. one is orange and the other is a tabby
0163
Stefan Matei @stefanmatei.com · 29/10/2025
stay vigilant!
Top text: “parents beware! some sicko is putting communist literature in the halloween candy!”

Image: books by Engels, Mao, Lenin, and Marx found inside a Snickers bar. 

Bottom text: “Please be on the lookout!”
020
Stefan Matei @stefanmatei.com · 02/10/2025
Was disappointed to see the Whiteboard Guy giving the Vercel CEO fodder to reply to (Vercel guy bumps up his positive comments to push down the negative ones after his Netanyahu photo). He deleted it the next day (after at least one person got upset) but decided DHH is still OK to fraternize with.
Vercel CEO: Announcing @vercel's Series F to build the Al Cloud

Ben Holmes: Press (series) F to pay respects

A third person replying: Also, what a beautiful day to cancel @warpdotdevDHH: You should checkout Omarchy, and come help us make it better!

Ben Holmes: Best ad I've gotten in my replies 👏 I’m tempted!
081
Stefan Matei @stefanmatei.com · 30/09/2025
similar energy
hamas evilly kills former babies, while the heroic IDF targets only future adults

Jul 22
Amit Cohen, who was killed yesterday in Gaza, was once a face known to every Israeli — in 2007, he was a Huggies baby.
Photo of a soldier. 
Photo of a baby.
010
Stefan Matei @stefanmatei.com · 30/09/2025
🎶 mammas don’t let your babies grow up to be coders 🎶
headline from cybernews.com: 

Australia thinks GitHub is as risky for kids as TikTok
Published: 25 September 2025
000
Stefan Matei @stefanmatei.com · 02/09/2025
That’s an explicitly pro Israel labeler. The labeler I’m using (that put these labels on pef-moderation) is @shadowbanner.bsky.social — it’s not perfect but it’s less biased in favor of Israel.
pef-moderation labelled with: Liberal Apologia, Gaza Genocide Denier, Israel Apologia, Denies U.S. Genocide Role
271
Stefan Matei @stefanmatei.com · 02/09/2025
Reposting with alt text: 86% of the world’s genocide experts have declared that Israel is committing genocide in Gaza. Israel made its genocidal intent clear from day one. It then committed genocide in full view of the world. Nobody with power or influence can say they didn’t know at the time.
Therefore, the International Association of Genocide Scholars:

Declares that Israel's policies and actions in Gaza meet the legal definition of genocide in Article II of the United Nations Convention for the Prevention and Punishment of the Crime of Genocide (1948);

Declares that Israel's policies and actions in Gaza constitute war crimes and crimes against humanity as defined in international humanitarian law and the Rome Statute of the International Criminal Court;

Calls upon the government of Israel to immediately cease all acts that constitute genocide, war crimes and crimes against humanity against Palestinians in Gaza, including deliberate attacks against and killing of civilians including children; starvation; deprivation of humanitarian aid, water, fuel, and other items essential to the survival of the population; sexual and reproductive violence; and forced displacement of the population;

Calls upon the government of Israel to comply with the Provisional Measures orders of the International Court of Justice;

Calls upon the state parties of the International Criminal Court to comply with their obligations, cooperate with the Court, and surrender any individual subject to an arrest warrant;

Calls upon all states to actively pursue policies to ensure respect for their obligations under international law, including under the Genocide Convention, the Arms Trade Treaty and international humanitarian law, with regards to Israel and Palestine;

and

Calls upon the government of Israel and all other United Nations members to support a process of repair and transitional justice that will afford democracy, freedom, dignity, and security for all people of Gaza.

Current as of 28 July 2025
Resolution passed 31 August 2025
030
Stefan Matei @stefanmatei.com · 02/09/2025
Nice! Just a heads up, it looks like the last two demos aren’t highlighting anything (only tried on iOS so far)
Trying to search for the word “intriguing” and it’s not highlighted in the text below
100
Stefan Matei @stefanmatei.com · 31/08/2025
000
Stefan Matei @stefanmatei.com · 31/08/2025
Went to a pro Palestine march/protest in Bucharest
Two people wearing black masks and keffiyehs take a selfie at night. A Palestinian flag is visible behind them
220
Stefan Matei @stefanmatei.com · 18/08/2025
“a closed box” 🤯 now i realize why one of my icons felt off. the box wasn’t closed enough compared to the ios share icons. not as off as bluesky’s icons though.
screenshot of an audio player in an ios browser. there are 2 “share” icons visible: the native one (in the browser bar) has an up pointing arrow coming out of an enclosed box; the second share icon (next to the current time in the audio player) has an up pointing arrow coming out of a box that is not closed off at the top.
130
Stefan Matei @stefanmatei.com · 13/08/2025
A pattern I’m using to co-locate (adopted) stylesheet files with web component scripts until CSS module scripts—e.g: `import sheet from './styles.css' with { type: 'css' };`—become available outside of Chrome: 🔗 github.com/nonsalant/co...
class MyComponent extends HTMLElement {
    static cssPaths = ['my-component.css'];

    constructor() {
        super().attachShadow({ mode: 'open' }).innerHTML = `<h1>Hello World</h1>`;
        // Current shadow root or the first parent shadow root or 'document'
        this.assetHost = this.shadowRoot ?? this.getRootNode();
    }

    connectedCallback() {
        this.addAssets();
    }

    async addAssets() {
        const Ctor = this.constructor; // to access static stuff from non-static method
        const cssPaths = Ctor.cssPaths;
        const stylesheets = await Ctor.addCss(...cssPaths);
        this.assetHost.adoptedStyleSheets.push(...stylesheets);
    }

    static basePath = import.meta.resolve('./');

    // Load CSS files and cache the stylesheets statically
    static cssPromiseCache = new Map();
    static async addCss(...stylesheetPaths) {
        const stylesheets = [];
        for (let path of stylesheetPaths) {...
        }
        return stylesheets;
    }
}

export async function createStylesheet(cssText) {
    const stylesheet = new CSSStyleSheet();
    await stylesheet.replace(cssText);
    return stylesheet;
}
120
Stefan Matei @stefanmatei.com · 07/08/2025
cool
Screenshot from Dev Tools showing “limited availability” status when hovering the CSS hyphenate-limit-chars property.
030
Stefan Matei @stefanmatei.com · 05/08/2025
Mickey Doyle from Boardwalk Empire nervously laughing
000
Stefan Matei @stefanmatei.com · 04/08/2025
And I started this off making the fallback for the custom element not getting defined (or javascript not loading) a horizontal scroller with the 2 images side by side, but now I found a way to make the scrollbar always visible on Mac OS (Chrome and Safari): codepen.io/nonsalant/pe...
Demo of pure CSS fallback: Side by side images with a chunky horizontal scrollbar visible below
100
Stefan Matei @stefanmatei.com · 31/07/2025
@kevinpowell.co really used `color-scheme: dark light` on his glasses 🤓 😎 #css
A layout with some cards in dark mode and the webcam video in a corner showing Kevin wearing regular glasses 🤓 A layout with some cards in light mode and the webcam video in a corner showing Kevin wearing CGI sunglasses 😎
1171
Stefan Matei @stefanmatei.com · 28/07/2025
just a heads up, looks like someone who doesn’t get sarcasm mislabeled you with the “Dearborn Scapegoating” label. CC: @shadowbanner.bsky.social
Dearborn Scapegoating

Blames Muslim voters in Dearborn, Michigan for the 2024 U.S. election results, and Kamala Harris's loss. 'Dearborn' used as a catch-all term for all Muslim-American voters.

Source: @shadowbanner.bsky.social
110
Stefan Matei @stefanmatei.com · 24/07/2025
Correction on a previous thread I made about using AbortController in a web component (to clean up anonymous arrow function events from external elements):
In the constructor():

this.#abortController = new AbortController();
this.#abortSignal = { signal: this.#abortController.signal }


In the connectedCallback():

trigger.addEventListener(
    'click',
    this.handleClick.bind(this),
    this.#abortSignal
);

['mouseenter', 'focus'].forEach(e => {
    trigger.addEventListener(
        e,
        () => this.loadAssets(),
        this.#abortSignal
    );
});


In the disconnectedCallback():

this.#abortController.abort();
120
Stefan Matei @stefanmatei.com · 14/07/2025
Oh, it’s the first time I see this! For what it’s worth, it shouldn’t be empty, but a whitespace: dev.to/afif/what-no...
Yes you can do the following:

.box {
  --color: ;
  background:var(--color); 
}
The above is valid as per the specification:

Note: While <declaration-value> must represent at least one token, that one token may be whitespace. This implies that --foo: ; is valid, and the corresponding var(--foo) call would have a single space as its substitution value, but --foo:; is invalid.

Pay attention to the last sentence because we need to have at least one space. The below is invalid:

.box {
  --color:;
  background:var(--color); 
}
010
Stefan Matei @stefanmatei.com · 12/07/2025
Sometimes I add extra keyboard shortcuts or access with alternative keys (besides the arrows) to things just to get an excuse to use <kbd>’s, like I did on codepen.io/nonsalant/fu...
Screenshot of a map with zoom in/out buttons, the text above it says:

📍 Select a location
Click on the map to place a pin at your location. Use + and - to zoom in and out (press H to view all keyboard shortcuts).Screenshot from a game of tic-tac-toe. The text below the game board says:


* Symbols disappear after 4 moves
As indicated by the depleting circles above each symbol.

⌨️ Keyboard navigation
Navigate using the arrow keys or W, A, S, and D. Use the Spacebar to place symbols. Note: use Tab to navigate into the game board first.
021
Stefan Matei @stefanmatei.com · 10/07/2025
I made some enhancements to my web component that loads other web components in popovers. You can now lazy load the markup inside the popover too (not just its script(s) and style(s)) from an external html file: github.com/nonsalant/la...
Screenshot from CodePen. The demo allows you to interact with two third party web components (storage-form and action-table) lazily loaded from CDNs when opened in popover modals.
251
Stefan Matei @stefanmatei.com · 21/05/2025
just used the dynamic import version of this (import attributes to import JSON from a module) in a web component method. very demure
async fetchData() {
        if (!this.stationId) return;
        const weatherStationUrl = `${this.dataPath}${this.stationId}.json`;
        const data = await import(weatherStationUrl, { with: { type: "json" } });
        const stationData = data.default;

        const spring = stationData.springFreezeDates;
        const fall = stationData.fallFreezeDates;
        const freezeFreeDays = stationData.freezeFreeDays;
        const stationMeta = stationData.meta;

        this.lastFrostDate.innerHTML = `<strong>${formatDateLong(spring[32][50])}</strong>`;
        this.firstFrostDate.innerHTML = `<strong>${formatDateLong(fall[32][50])}</strong>`;
        this.freezeFreeDays.innerHTML = `<strong>${freezeFreeDays}</strong>`;
120
Stefan Matei @stefanmatei.com · 08/05/2025
Tim O’Brien (illustrator whose work appeared on the cover of Time magazine and whose website I built a number of years ago) predicted this! obrienillustration.com/american-pope/
Pope wearing American flag
000
Stefan Matei @stefanmatei.com · 07/05/2025
And now that she won a Pulitzer, the washington post — from where she resigned after they censored her bezos cartoon — appear quite happy to count it as a “win” for themselves
Posted on WaPo’s Bluesky account:

“The Washington Post wins two Pulitzer Prizes. The honors are for breaking news reporting and illustrated reporting and commentary.”
000
Stefan Matei @stefanmatei.com · 01/05/2025
Musk, who was wearing a hat on top of another hat: “Well, Mr. President, you know they say I wear a lot of hats” 🎩 🧢 👒
Screenshot from CNN Business:

“You have been treated unfairly, but the vast majority of people in this country really respect and appreciate you, and this whole room can say that very strongly. You’ve really been a tremendous help,” Trump told Musk Wednesday. “You’re invited to stay as long as you want. At some point, I guess, he wants to get back home to his cars.”
Musk, who was wearing a hat on top of another hat, quipped, “Well, Mr. President, you know they say I wear a lot of hats.”
100
Stefan Matei @stefanmatei.com · 25/04/2025
I’ve been using CSS Layers in production* since late 2022, and wish I would’ve had a nice boilerplate like this one by @‪matuzo.at‬ as an example. Screenshots: my layer structure — messy w/ lots of layers vs Manuel’s more organized structure using nested layers.
@layer reset, accessibility, animated-entrances, utility, forms;
@layer contract, editor-ui, editor-ui-overrides, generator;
@layer signature;
@layer from-quill-editor, from-quill-editor-overrides, quill-html-button;
@layer signatures, buttons;
@layer modal, preview-modal;
@layer contract-typography;
@layer button-animations;
@layer repeater;@layer core, third-party, components, utility;
@layer core.reset, core.tokens, core.base;
@layer third-party.imports, third-party.overrides;
@layer components.base, components.variations;
110
Stefan Matei @stefanmatei.com · 22/04/2025
still broken on mobile
screenshot of the second image where the bluesky “back” arrow is covering the first word(s?)
000
Stefan Matei @stefanmatei.com · 12/04/2025
To those talking shit about Google for firing someone like Adam—listen, these are uncertain times and they’ve had to make some tough but rational decisions about their priorities, I’m sure we’ll soon see how it was all in the best interest of their users and the web as a whole
Adam Argyle posted that his role at Google was eliminated. Screenshot from TechCrunch: “Google is allegedly paying some AI staff to do nothing for a year rather than join rivals”
020
Stefan Matei @stefanmatei.com · 04/04/2025
Quoting tweet from Mehdi Hasan:

Cory Booker took a smiling photo in his office with former Israeli defense minister Yoav Gallant, wanted by the ICC for war crimes, in front of a picture of Frederick Douglass.

Quoted tweet from Yoav Gallant in December 2024:

Thank you to my friend, Senator Cory Booker for hosting an important discussion on the powerful alliance between Israel and the United States, which translated into unprecedented support during the 7-front war.
121
Stefan Matei @stefanmatei.com · 03/04/2025
@matuzo.at looks like type=search field is being made invisible on iOS (ss is from Chrome on an iPad)
type=search field being made invisible compared to the default UA styles where the field is visible
100
Stefan Matei @stefanmatei.com · 29/03/2025
css-tricks.com/solved-by-cs...
A labeled diagram with a color-coded legend. The legend distinguishes between "Scoped" (represented in dark orange) and "Non-scoped" (represented in light pink). The diagram itself resembles a rectangular container with an inner section labeled "hole" (colored in light pink) and an outer section labeled "donut" (colored in dark orange).
110
Stefan Matei @stefanmatei.com · 11/03/2025
Tooltips & popovers in Chrome 133→ goo.gle/43cS7ym
163
Stefan Matei @stefanmatei.com · 22/02/2025
nice! consider adding width and height attributes for the images to reduce layout shift (www.smashingmagazine.com/2020/03/sett...)
000
Stefan Matei @stefanmatei.com · 20/02/2025
Just found out you can press and hold the Chrome address bar on iOS to change its position to the bottom
Long pressing the iOS Chrome address bar shows options to copy, search for copied text, and to “move address bar to bottom”
000
Stefan Matei @stefanmatei.com · 11/02/2025
Unless you’re trying to use them like shortcodes to write less html, i.e. by just putting data in attributes. Which I wouldn’t recommend for actual content (and not UI for example) due to the classic drawbacks of SPA’s (layout shift, harder to parse for search engines, reliance on JS, etc).
<book-item data-title=… data-description=… data-img=… data-img-alt=…></book-item>
110
Stefan Matei @stefanmatei.com · 11/02/2025
If your book gallery doesn’t have any JS itself, the web component may not need any JS (at all) either. See, for example: hawkticehurst.com/2024/11/css-... The structure for your CSS web component could look something like this image.
book-gallery (custom element)
* book-item (custom el)
 * * book-cover > img
 * * h2
 * * p
* book-item (custom el)
 * * book-cover > img
 * * h2
 * * p
221
Stefan Matei @stefanmatei.com · 10/02/2025
Now in the trending tab 🎉 (also cleaned up the markup so JS only UI is added by JS and unique checkbox ID’s are generated by JS if not provided)
Notification from CodePen saying they “picked” my web component pen called Category Toggles
000
Stefan Matei @stefanmatei.com · 09/02/2025
Yesterday I rewrote another piece of a client project as a web component (<toggle-categories>) for last week’s @codepen.io challenge codepen.io/nonsalant/pe...
Screenshot from CodePen with HTML code on the left and preview on the right. The preview asks “What happens to a category toggle when its children are neither all on nor all off?” #buildinginpublic
020
Stefan Matei @stefanmatei.com · 07/02/2025
yay! thanks @darn.es for merging my PR for this into the 'awesome list' 🙏 it was my first to a repo that wasn't mine and was wondering if i was doing things right 😅 github.com/davatron5000...
Screenshot from the pull request to the list of framework-agnostic standalone web components.

nonsalant: A button to print only a specific element (instead of the whole page)
daviddarnes: Love it!
daviddarnes merged commit
131
Stefan Matei @stefanmatei.com · 04/02/2025
had to build something that looks like an on/off toggle but has a secret third state (pay attention to the small toggle next to the herbs category—its children aren’t always all in the same boat so it needs an intermediary state)
010
Stefan Matei @stefanmatei.com · 26/01/2025
Let’s not forget Alexandria Working Tirelessly Toward a Ceasefire Coretz was one of these people
The Serfs: “I'll never get over people unwilling to direct their anger at politicians or corporations doing an active genocide”
110
Stefan Matei @stefanmatei.com · 20/01/2025
Screenshot from The Independent with the headline “David Lynch says he can no longer 'go out' due to Covid fears after health diagnosis.”

Subhead says “Film director suggested he might never direct again”
000
Stefan Matei @stefanmatei.com · 16/01/2025
also works with remote URL’s (and the base itself can be a remote address), as long as the remote server allows CORS and any web component’s tag can be renamed by adding a pipe separator and a new name after its filename
<script type="module" src="../definer.js?load=
        my-component|external-component,
        my-other-component|other-external-component,
    &base=https://web-component-definer.netlify.app
"></script>
100
Stefan Matei @stefanmatei.com · 10/01/2025
not sure yet how practical this would be (seems really useful to me rn), but what if there was just 1 file responsible for loading web components from their individual files, and you could tell it what to load (and how to define each wc) using query strings? got this working, will put online shortly
<script src="./web-component.js?file=my-component" type="module"></script>
<script src="./web-component.js?file=my-other-component" type="module"></script>
<script src="./web-component.js?define=renamed-component&file=my-component" type="module"></script>

<my-component></my-component>
<my-other-component></my-other-component>
<renamed-component></renamed-component>
101
Stefan Matei @stefanmatei.com · 09/01/2025
there’s a bit of weirdness when i go to the home page and then tap on the article on iOS. (missing viewport meta? although it works if you open the article directly)
screenshot of article page showing the content zoomed in
100