Sr. Content Developer at Microsoft, working remotely in PA, TechBash conference organizer, former Microsoft MVP, Husband, Dad and Geek.
155801 stories
·
33 followers

The golden rule of Customizable Select

1 Share

Customizable select is coming to Safari 27. With this technology, developers can fully control the appearance of <select> elements — custom arrows, option layouts, color swatches, icons, full visual styling — without the need for JavaScript libraries or an endless parade of <div> elements. And because it’s a built-in control, you don’t have to compromise on keyboard navigation or accessibility semantics.

But, to ensure this built-in control works well for everyone, it’s important to follow this single but essential rule: always provide text content or accessible text attributes for your option elements.

Every time that rule is broken, every time an option is styled to show a visual without any text and without any accessible fallbacks, three different problems get introduced all at once. The menu is harder to use for everyone, impossible to use with accessibility tools, and it becomes a completely broken experience in browsers that don’t support it yet.

When you remember to follow the rule, you’ll improve the user experience, support accessibility, and provide progressive enhancement so it works for people regardless of what browser they choose.

We’ll show you why following this mission critical rule gets you:

Better UX

Take this category filter from a photographer’s gallery site. The version below uses icons alone — a building, a flower, a hummingbird — to represent each category:

customizable select of categories for photo gallery with no labels

It looks clean. But a user who doesn’t immediately recognize what the hummingbird icon represents has no fallback. The closed select shows only an icon in the button, with no other hint of what’s currently selected. Add a text label to each option and the experience becomes immediately scannable. The selected state is readable at a glance, and every option is unambiguous:

customizable select of categories for photo gallery with labels

The icons are still there. The labels make it readily decipherable for everyone.

Better accessibility

When a screen reader encounters an option with no text, the user may not hear a descriptive label for each option. Braille rendering and other assistive technology output may also be confusing. Text, even when hidden visually with a .visually-hidden class, stays in the accessibility tree and gives screen readers, braille displays, and speech recognition software something real to work with. If you use an icon as an <img>, add an alt or aria-label — or mark it decorative using alt="" and let the visible or visually-hidden label carry the meaning.

<option>
   <img src="bird.svg" alt="">
   <span>Wildlife</span>
</option>

The problem you solve isn’t just a compliance checkbox: it’s the difference between a visitor completing your form and someone abandoning it.

Better progressive enhancement

Customizable select is a new feature. Browsers that don’t yet support it fall back to the platform-native <select> — which is exactly the right behavior, as long as your options still make sense in that fallback state.

If you’ve removed text in favor of icons or swatches, a user on an older browser sees a dropdown full of empty options. The same is true when CSS fails to load at all: a slow connection, a corporate proxy stripping stylesheets, a user with custom styles enabled. Wrap your enhancements in @supports (appearance: base-select) and keep plain text as your baseline. Adding a swatch is an enhancement. Removing the color name to make room for it is a regression.

The rule for maximizing the power and utility of customizable select is simple: keep the text. You can hide it visually. You can make it tiny. You can position it off-screen. But it needs to be there. Icons, swatches, and illustrations are additions to an option — never substitutes for it. Follow that rule and the rest of customizable select is yours to play with.

Feedback

We love hearing from you. To share your thoughts, find our web evangelists online: Saron Yitbarek on BlueSky, Jen Simmons on Bluesky / Mastodon, and Jon Davis on Bluesky / Mastodon. You can follow WebKit on LinkedIn. If you run into any issues, we welcome your feedback on Safari UI (learn more about filing Feedback), or your WebKit bug report about web technologies or Web Inspector. If you run into a website that isn’t working as expected, please file a report at webcompat.com. Filing issues really does make a difference.

Read the whole story
alvinashcraft
18 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

Creating a multi-agent application – Part 5 (final)

1 Share

In part 4 of this series we created our final two agents. In this final part of the series we’ll review the workflow that we create with the StateGraph class of LangGraph.

In the following code we pass the ResearchState object to StateGraph and create a node for each agent and edges that represent transitions between the nodes. There is one conditional edge: if the reviewer rejects the draft, it goes back to the author

workflow.add_node("blogger", blogger_node)
workflow.add_node("researcher", research_node)
workflow.add_node("author", author_node)
workflow.add_node("reviewer", reviewer_node)

workflow.set_entry_point("blogger")

workflow.add_edge("researcher", "author")
workflow.add_edge("author", "reviewer")

# Add conditional edges from blogger
workflow.add_conditional_edges(
    "reviewer",
    lambda state: "author" if state.get("review_result") == rejected else "blogger"
    {
        "author": "author",
        "END": END
    }
app = workflow.compile()

That’s it. All that’s left is to take it out for a spin.

Read the whole story
alvinashcraft
32 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

Shiny Health 2.0 — The Ultimate Cross-Platform Health Library

1 Share
One IHealthService for Apple HealthKit and Android Health Connect — now with 30+ data types across activity, body, vitals, nutrition, reproductive/cycle tracking and workouts, real-time observation, and a Microsoft.Extensions.AI tool surface so an LLM can read and write your health data.
Read the whole story
alvinashcraft
38 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

A NEW CameraView to Rule Them All

1 Share
One CameraView for .NET MAUI (iOS, Android, Windows, macOS AppKit) and Blazor WebAssembly — live preview, photo/video capture, 11 live filters, and a frame-analysis pipeline that scans barcodes, boxes faces, detects motion, runs OCR, and parses invoices, receipts, driver's licenses, health cards, credit cards, and passports into strongly-typed records.
Read the whole story
alvinashcraft
42 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

Strongly typed generic object in C#

1 Share

Because sometimes you just need bit more type safety.

Read the whole story
alvinashcraft
54 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

Event Sourcing: temporally misplaced or duplicated events

1 Share

Event sourcing is easy. Just store what happened to a thing as events, and when you need the thing’s state, project the events. Unless it is not because there are misplaced or duplicated events in the event stream. In the tenth part of my event sourcing series, we’ll look at why events can be misplaced and how to handle them.

What a misplaced event is

I guess I invented the term misplaced event while writing this blog post. I didn’t find an existing term for this.

A misplaced event is an event that is at the wrong place on the event stream. Over our eleven years of using event sourcing, we encountered a couple of these.

How events get misplaced or duplicated

In our system, there exist two reasons for events to get misplaced.

The first reason is that a user enters the wrong effective date for a bi-temporal event. Of course, we try to detect these during command validation, but there are scenarios when this is really hard – especially when the user can undo previous actions. For example, in our time-tracking system, users can define an organisational chart. A so-called organisation form that consists of multiple organisation units (as explained in the last two posts in this series). A unit has a creation date from which on it exists in reality. But maybe this date changes or was entered incorrectly. So we need to be able to change this date. Furthermore, there might be update events that change a unit’s property. Now, it is possible that, after a couple of changes, the update event is before the creation date.

The more common reason for misplaced or duplicated events, however, is a technical one: our system misbehaved. Maybe the backend was unresponsive, we programmed a bug into the code, there was a network hiccup that led to retries by the client and/or the user. Of course, we try to provide a stable, responsive, quality system. But sometimes things go wrong.

How to deal with misplaced or duplicated events

We try to detect misplaced events and ignore them. Maybe you remember that our projector does lifetime management. The projector tracks the creation and deletion of the thing. The projector distinguishes among creation, update, and deletion events. So, the projector can ignore events in a couple of situations:

  • Update events before any creation event: the update event gets ignored because there is no current value to update.
  • Update events after deletion event: gets ignored because there is no need to update the deleted data.
  • Deletion events before any creation event are ignored.
  • Duplicated creation events, or multiple creation events without a deletion event in between: only use the first creation event.
  • Duplicate deletion events without any creation events in between get ignored.

We can’t detect duplicate update events, but they’re typically not a problem because they just set the data to the same value again.

Another approach to preventing duplicate events would be to compare them. If two adjacent events are equal (except for the event ID), we could ignore one of them. However, there are rare scenarios in which these equal events are both real. While deduplicating these events into a single event does not change the resulting value, it could matter when we are interested in the number of events for some reason.

Looking back, we had a good experience with our approach. And keep in mind that misplaced or duplicated events should be rare cases.

Summary

When an event-sourced system runs for many years, sometimes things go wrong. One consequence we saw in our system is that there are events that are misplaced or duplicated. Even if rare, we need a way to handle these events. We decided to use our projector’s lifetime management capabilitiesto handle these cases.

Deep dive

Let’s look at some tests again:

These tests check that updates that are before a create event or after a deletion event are ignored.

I know, you’ve already seen such tests, so let’s expand the deep dive to the parsing of the events and the projection:

I simplified the code a bit by removing things we haven’t discussed yet (the red dots indicate the missing lines).

The parsing is done by simply splitting, trimming, and matching the input to fixed constants. Simple and good enough.

The EventsForProjection type is used to give a list of events more meaning.

A value of the type EventsForProjection holds a list of events from a single event stream that can be projected into a single value (or a single timeline). A value of the type EventsForProjections holds events from several event streams and must be projected into several values (or timelines). To separate the individual streams, we need to pass a function to the projector that specifies how to identify events belonging to the samestream. Typically, this is a function that returns the event stream ID for an event:

When projecting multiple organisational forms, the projector first groups the events by their OrganisationFormId and then projects all the events per group.

The EventsForProjection.shake function in the parsing function changes the order of the events so that we know that the events are sorted correctly:

This way, we can write the events in the test data in the correct order, making the test easier to understand while still ensuring the implementation sorts correctly.

Read the whole story
alvinashcraft
1 minute ago
reply
Pennsylvania, USA
Share this story
Delete
Next Page of Stories