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

Algorithmic hover states with contrast-color()

1 Share

Firefox 146 added support for contrast-color() joining Safari 26 in the First Implementor’s Club. For those unfamiliar, contrast-color(<color>) is a new CSS function that will take a <color> as input and returns either white or black depending on which has the most contrast.

The quintessential example is choosing a foreground text color with the best contrast.

button {
	--button-bg: red;
	background: var(--button-bg);
	color: contrast-color(var(--button-bg)); 
	/* @returns black (5.25:1 WCAG AA Pass) 
			not white (3.99:1 WCAG AA Fail) */
}

If someone changes --button-bg to purple, the foreground color automatically resolves to the either white or black, whichever has more contrast. It avoids having to set an extra token for color and takes the guess work out of picking an accessible foreground color.

I think this is going to be an incredible boon to design systems where I don’t control what --button-bg is, but I do care about providing accessible experiences. And I think that’s the goal of this feature; to have “smart defaults” that lead to more accessible websites, easier algorithmically-driven color systems, and better “theme a whole website from a single color picker” demos.

Sure contrast-color() can do foreground colors, but what about backgrounds?

We’re having conversations at work about algorithmically driven rest/hover/active states for Buttons. In the current Baseline you can use color-mix() to lighten/darken colors on :hover

button {
	background-color: var(--button-bg);
	color: color-contrast(var(--button-bg));

	&:hover, &:focus {
		background-color: color-mix(
			in srgb,
			var(--button-bg) 90%,
			black 10%
		)
	}
}

The code above will dim your button on :hover 10% by mixing in black. But what if our Buttons are already dark (black, navy, etc)? In that situation we want to lighten the background-color instead of dimming. We can glue on new classes like button.lighten-on-hover or button.invert-hover and that works… until we get to light and dark theme modes of our Button where you probably want to lighten/darken oppositely depending on the mode…

Ugh. In that situation you’d have @media (prefers-color-scheme: dark), [data-theme="dark"] styles in your Button styles and people are mad now because the Button styles are too complex. There’s got to be a better way!

Well, do I have good news for you…

See the Pen contrast-color() powered lighten/darken bg on hover by Dave Rupert (@davatron5000) on CodePen.

Per my previous conversations, I wondered if we could use contrast-color() to programmatically lighten/darken an button based on its current background-color. If the Button is black, and the contrast-color is white, let’s mix in white (and vice versa):

:root {
	color-scheme: light dark;
	--button-bg: light-dark(navyblue, lightpurple);
}

button {
	background-color: var(--button-bg);
	color: color-contrast(var(--button-bg));

	&:hover, &:focus {
		background-color: color-mix(
			in srgb,
			var(--button-bg) 75%, 
			contrast-color(var(--button-bg)) 10%
		)
	}
}

Huzzah! Now our Button’s hover states go in the desired direction and our foreground color is intrinsically styled based on its own background-color. Nice. From a design systems perspective I’m pretty excited about the possibility to remove a bunch of state-based tokens from our collection.

Now available in… everywhere?

This approach only works in Safari and Firefox. However, if I use Lea Verou’s method of polyfilling contrast-color(), we can pop in a custom @function --contrast-color() that works in Chromium 139+. The final working solution looks like this:

/* @function supported in Chromium */
@function --contrast-color(--bg-color) {
  --l: clamp(0, (l / var(--l-threshold, 0.623) - 1) * -infinity, 1);
  result: oklch(from var(--bg-color) var(--l) 0 h);
}

button {
  /* contrast-color() supported in Safari & Firefox */
  --button-fg: contrast-color(var(--button-bg));
  
  @supports not (color: contrast-color(red)) {
    --button-fg: --contrast-color(var(--button-bg));
  }
  
  background: var(--button-bg);
  color: var(--button-fg);
  
  &:hover,
  &:focus{
    background-color: color-mix(
      in srgb,
      var(--button-bg) 75%,
      var(--button-fg) 10%
    );
  }
}

Depending on your browser matrix, this may work for you. It’s probably a smidge too new for us to roll out to customers, right now but for personal sites heck yeah.

This is interesting tech and I’m excited to dig in more. And spoiler alert, this is the first post in a small little series I have already written up for you.

Read the whole story
alvinashcraft
just a second ago
reply
Pennsylvania, USA
Share this story
Delete

AI and the Future of Work: 5 Predictions for 2026

1 Share

Experts predict 2026 will bring less AI hype and more governance, delayed enterprise spending, AI moving into OT, smarter cyberattacks, and faster cooling tech.

The post AI and the Future of Work: 5 Predictions for 2026 appeared first on TechRepublic.

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

Most dubious uses of AI at CES 2026

1 Share
A palm-sized device with a small screen showing a cartoonish avatar of Studio Ghibli’s Miyazaki next to the character Totoro.
Let’s take bets on how much Hayao Miyazaki would hate this.

You can't shake a stick without hitting an AI gadget at CES this year, with artificial smarts now embedded in just about every wearable, screen, and appliance across the show floor, not to mention the armies of AI companions, toys, and robots.

But those are just the beginning. We've seen AI pop up in much stranger places too, from hair clippers to stick vacs, and at least one case where even the manufacturer itself seemed unsure what made its products "AI."

Here are the gadgets we've seen at CES 2026 so far that really take the "intelligence" out of "artificial intelligence."

Glyde smart hair clippers

This is a product that would be si …

Read the full story at The Verge.

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

C# Grabs Language of the Year, TIOBE Predicts TypeScript Rise

1 Share
C# was named TIOBE’s Programming Language of the Year, while the index also pointed to a potential rise for TypeScript. Here’s how the latest rankings compare across TIOBE, RedMonk, and PYPL.
Read the whole story
alvinashcraft
52 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

EF Core Lazy Loading Performance Gotcha

1 Share

I was recently using EF Core's ILazyLoader for lazy loading without proxies, and ran into a performance issue that took me by surprise. When you call DbSet<T>.Add() to add an entity to the context, EF Core immediately injects the lazy loader into your entity even before you've called SaveChangesAsync(). This means if you navigate to a lazy-loaded navigation property before persisting, EF Core will try to query the database for related entities that don't exist yet.

It's an unnecessary performance overhead and the fix is fortunately very simple: don't add entities to the DbContext until right before you're ready to call SaveChangesAsync().

The Model

To understand how it behaves I created a simple example project using a Blog and Post relationship with ILazyLoader injection:

public class Blog
{
    private ICollection<Post>? _posts;
    private ILazyLoader? _lazyLoader;

    public Blog() {}

    public Blog(ILazyLoader lazyLoader)
    {
        _lazyLoader = lazyLoader;
    }

    public int Id { get; set; }
    public required string Name { get; set; }
    
    public virtual ICollection<Post> Posts
    {
        get => _lazyLoader?.Load(this, ref _posts) ?? _posts ?? [];
        set => _posts = value;
    }
}

public class Post
{
    public int Id { get; set; }
    public required string Title { get; set; }
    public required string Content { get; set; }
    public virtual Blog? Blog { get; set; }
}

Reproducing The Problem

Now let's look at what happens when you add a blog with posts, but navigate into the Posts collection before persisting to the database:

using (var context = new BloggingContext())
{
    await context.Database.EnsureCreatedAsync();

    // Create a new Blog with two Posts
    var blog = new Blog
    {
        Name = "Test Blog",
        Posts =
        [
            new Post { Title = "First Post", Content = "Hello from EF Core 10!" },
            new Post { Title = "Second Post", Content = "Another post for testing." }
        ]
    };

    // This causes EF Core to inject the lazy loader using reflection
    context.Blogs.Add(blog);

    // Accessing blog.Posts triggers the lazy loader to query the database
    // even though this blog hasn't been saved yet!
    Console.WriteLine("Number of posts: " + blog.Posts.Count);

    await context.SaveChangesAsync();
}

When you call context.Blogs.Add(blog), EF Core uses reflection to inject an ILazyLoader instance into the Blog object. From that point on, any access to blog.Posts will trigger the lazy loading mechanism. Since the blog doesn't exist in the database yet (no Id has been assigned), EF Core will execute a query that looks something like:

SELECT [p].[Id], [p].[BlogId], [p].[Content], [p].[Title]
FROM [Posts] AS [p]
WHERE [p].[BlogId] = 0

This is completely pointless - the blog hasn't been persisted, so there can't possibly be any related posts in the database.

The Solution

The fix is straightforward: only add the entity to the context right before you call SaveChangesAsync():

using (var context = new BloggingContext())
{
    await context.Database.EnsureCreatedAsync();

    var blog = new Blog
    {
        Name = "Test Blog",
        Posts =
        [
            new Post { Title = "First Post", Content = "Hello from EF Core 10!" },
            new Post { Title = "Second Post", Content = "Another post for testing." }
        ]
    };

    // Do all your work with the blog object first
    Console.WriteLine("Number of posts: " + blog.Posts.Count);

    // Only add to context when you're ready to save
    context.Blogs.Add(blog);
    await context.SaveChangesAsync();
}

Now when you access blog.Posts, there's no lazy loader injected yet, so it just returns the collection you assigned, with no database query needed.

Summary

If you're using ILazyLoader injection in EF Core, be mindful of when you add entities to the DbContext. The lazy loader gets injected as soon as you call Add(), not when you call SaveChangesAsync(). So if you need to work with navigation properties before persisting, keep the entity disconnected from the context until you're ready to save. This avoids unnecessary database queries.

Read the whole story
alvinashcraft
1 minute ago
reply
Pennsylvania, USA
Share this story
Delete

Reporting for duty: the story of IPipelineActivityReporter

1 Share

It’s a new year and it’s time for a new me! And that new me actually finishes the thoughts that she has. Last year, I spent quite a bit of time writing about the Aspire deployment story and alluded to the APIs it included for sending “activites” from the Aspire AppHost to the terminal based client. Let’s settle this topic once and for all and dive into these APIs and their evolution.

If you’ve been following my posts about Aspire Pipelines and the CLI redesign, you’ve seen the user-facing side of how deployment progress gets reported to the CLI. Today I want to talk about the API that makes all of that possible: IPipelineActivityReporter. This interface has gone through several iterations as we figured out how to model deployment activities, and its evolution mirrors the broader story of how deployment support in Aspire matured from basic callbacks to a full pipeline concept.

The Beginning: IPublishingActivityProgressReporter

Let’s rewind to June 2025 (I can’t believe I have to include the year now), during the Aspire 9.4 development cycle. We had just introduced the aspire deploy command and the DeployingCallbackAnnotation pattern I wrote about in my pipelines post. We needed a way for the AppHost to communicate deployment progress back to the CLI. The initial interface was called IPublishingActivityProgressReporter (yeah, that’s a mouthful).

The core concept was straightforward: the AppHost needed to tell the CLI “hey, I’ve started working on something” and then periodically update it with progress. The first version of this interface provided APIs for creating steps, updating their status, and signaling completion. It was functional but the name was…verbose. Within a week, we realized the error of our ways and renamed it to IPublishingActivityReporter. So at this point in time, the API shape that we were working with looked like this:

public interface IPublishingActivityProgressReporter
{
    Task<PublishingStep> CreateStepAsync(string title, CancellationToken cancellationToken);
    Task<PublishingTask> CreateTaskAsync(PublishingStep step, string statusText, CancellationToken cancellationToken);
    Task CompleteStepAsync(PublishingStep step, string completionText, CancellationToken cancellationToken);
    Task UpdateTaskAsync(PublishingTask task, string statusText, CancellationToken cancellationToken);
    Task CompleteTaskAsync(PublishingTask task, TaskCompletionState completionState, string? completionMessage = null, CancellationToken cancellationToken = default);
    Task CompletePublishAsync(bool success, CancellationToken cancellationToken);
}

As you can see, this initial design introduced a hierarchy: steps were the top-level units of work, and each step could contain multiple tasks. This maps pretty naturally to how deployments actually work. When you’re deploying to Azure, you might have a “provision infrastructure” step that contains tasks for provisioning each individual resource (CosmosDB, Storage, Container Registry, etc.). The step is the conceptual grouping, the tasks are the actual work items. One important thing to note here is that this hierarchy existed before the implementation of the Aspire Pipelines mentioned earlier. However, the possibility of these reporting steps eventually become real units of work did cross my mind and by the time I’m done writing you’ll see how the two converged.

The Sequential Problem

Let’s talk about the sequential problem, again.

The original implementation had a critical limitation that became obvious pretty quickly: everything was sequential. When you created steps, they executed one after another. When you created tasks within a step, same deal. This made sense for the initial Azure deployment implementation because that’s how we had structured the code—provision all infrastructure, then build all images, then deploy all compute resources.

But as we started working on the pipeline feature for Aspire 13, this sequential assumption became a problem. Pipelines are fundamentally about modeling dependencies and exploiting concurrency. If building the API service image doesn’t depend on building the frontend image, why wait? If provisioning CosmosDB doesn’t depend on provisioning Storage, why not do them in parallel?

The sequential UI also had practical problems. In the CLI, we rendered steps using Spectre’s task progress components, which looked nice but didn’t work well in non-interactive environments like CI/CD runners. The output would get mangled or lost entirely. We needed something that worked equally well whether you were deploying from your laptop or from a GitHub Actions runner.

If all this sounds familiar, that’s because it’s essentially the motivation behind the redesign CLI UI. What we are going to talk about next is the code change that enabled this.

The October Revamp

In October 2025, I tackled both problems with a significant revamp of the activity reporter. The changes touched three areas:

1. API changes to support concurrency

The interface stayed mostly the same, but the implementation fundamentally changed. Instead of assuming steps execute sequentially, the reporter now sends all updates over a Channel<PublishingActivity> that the CLI consumes asynchronously. This means multiple steps can be “in progress” at the same time, sending interleaved updates.

internal Channel<PublishingActivity> ActivityItemUpdated { get; } =
    Channel.CreateUnbounded<PublishingActivity>();

The reporter tracks all active steps in a ConcurrentDictionary and allows tasks to be created and updated within any step that’s still in progress. When a step completes, no more tasks can be added to it (we throw an exception if you try). This enforces a clean lifecycle while still allowing maximal parallelism across different steps.

2. CLI rendering for concurrent steps

I’ve talked about this before in my blog post about redesigning the CLI UI. This is how it looked in practice. We replaced the standard Spectre task progress components with a custom rendering approach that treated the activities from the AppHost as a continuous stream of events. Each step and task gets rendered with consistent formatting:

  • Arrow emoji (→) when a step starts
  • Checkmark (✓) when it completes successfully
  • Warning/error indicators for other completion states
  • Nested task updates displayed as indented lines under their parent step

This works great in both interactive and non-interactive contexts. In a terminal, you see the real-time updates. In CI logs, you get a clean sequential record of what happened.

3. Logging and Markdown support

We added a Log method to steps that allows arbitrary log messages to be emitted within a step’s context. These logs can optionally use Markdown formatting, which the CLI renders appropriately (with fallbacks for non-interactive environments). This turned out to be incredibly useful for surfacing diagnostic information during deployments without cluttering the task hierarchy. And Markdown was a nice touch for helping draw user attention to key portions of the log with the support of text formatting like bolding and hyperlinking.

Fun fact: the Markdown rendering is implemented via custom Spectre component that was largely AI-authored with some human testing and iteration.

void Log(LogLevel logLevel, string message, bool enableMarkdown);

Integration with Pipelines

The activity reporter revamp happened right as we were building out the pipeline feature because the two features were deeply intertwined. The pipeline needed a way to report the status of steps executing in parallel, and the activity reporter needed to support that use case.

One interesting design point: the pipeline itself doesn’t directly create steps in the activity reporter and the user doesn’t need to either when creating steps in the pipeline. Instead, each PipelineStep can optionally create its own reporting step when it executes. This keeps the concerns separated. The pipeline is responsible for dependency resolution and execution order, the activity reporter is responsible for communicating what’s happening to the client.

When a pipeline step executes, it gets access to a PipelineStepContext that includes a specific reporting step:

var builder = DistributedApplication.CreateBuilder();

builder.Pipeline.AddStep("migrate-db", (context) =>
{
    var reportingStep = context.ReportingStep;
    reportingStep.Log(LogLevel.Information, "Migrating database");
    // Execute migration here
    reportingStep.Log(LogLevel.Information, "Database migration completed");
});

builder.Build().Run();

Fin

The journey from IPublishingActivityProgressReporter to IPipelineActivityReporter reflects the broader evolution of deployment support in Aspire. What started as a simple need, to report progress to a CLI, became a concurrency-aware activity reporting system that integrates with pipelines and works in both interactive and non-interactive scenarios.

The core API itself ended up being deceptively small (just CreateTaskAsync and CompleteAsync and Log), but the implementation handles significant complexity: concurrent step execution with channel-based CLI communication, a step/task hierarchy with full lifecycle management, logging with Markdown support, and integration with InteractionService for mid-deployment user prompts. It’s a good example of how a simple abstraction can hide sophisticated machinery while remaining straightforward to use.

If you’re building custom deployment logic in Aspire and want to provide progress reporting, IPipelineActivityReporter is your friend. And now, you know a bit more about how it came to be.

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