• maiweb v0.1.0
  • ★
  • Feedback

#frontend

3 sources tagged with this.

  • CSS-Tricks
  • impressivewebs/frontend-feeds
  • web.dev
  • CSS-Tricks css-tricks.com css css-tricks frontend technology web-dev web-development 2026-06-12 15:08
    ↗

    One of those nuances to keep in your back pocket when writing for screen readers. There’s no need to include ‘navigation’ in your navigation labels originally handwritten and published with love on CSS-Tricks. You should really get the newsletter as well.

    Mark Underhill:

    And now to the reason I wrote this post: including the word “navigation” in your <nav> labels. There’s no need. If we did, we’d hear something like “Navigation, Primary navigation”. Not the end of the world, but unnecessarily repetitive for screen reader users.

    One of those nuances to keep in your back pocket when writing for screen readers. Reminds me, too, that there’s no need to say something like “image” when describing one in the alt text. That’s sorta implied. While I’m no screen reading native, I imagine these sorts of things are minor pet peeves that, given a little love and consideration, make navigating that much more enjoyable.

    While we’re on the UX of accessible text, another consideration: keep it succinct. It doesn’t have to be a novel.


    There’s no need to include ‘navigation’ in your navigation labels originally handwritten and published with love on CSS-Tricks. You should really get the newsletter as well.

    • GTA 6 - all you need to know about Rockstar's blockbuster game BBC News - Technology
    • What you need to know about the preliminary U.S.-Iran agreement signed by Trump NPR - Top Stories
    • What you need to know about the preliminary U.S.-Iran agreement signed by Trump NPR - Politics
    • Everything you need to know about the ABC Classic 100: Greatest of All Time ABC News (Australia)
    • What You Need to Know About How Tear Gas Harms Kids ProPublica
    • Microsoft 365 Security: Features You NEED to Know! #shorts How to Get an Analytics Job
    • Claude 4.8 - Three Things You Need To See Tyler Moore
    • Spring Security 7 Crash Course [2026] – Everything You Need to Know Amigoscode
    • You're writing twice as much CSS as you need to Kevin Powell
  • CSS-Tricks css-tricks.com css css-tricks frontend technology web-dev web-development 2026-06-12 15:09
    ↗

    Why isn't my 3D view transition working?! Sunkanmi tackles this frustration and offers an elegant fix for it. Why Isn’t My 3D View Transition Working? originally handwritten and published with love on CSS-Tricks. You should really get the newsletter as well.

    If you have played around with view transition a bunch, you may have noticed that 3D transitions between two pages (i.e., cross-document view transitions) don’t seem to work. That is, at least not without the browsers flattening things first.

    Image elements are the best example to demonstrate this because, like the snapshots a browser takes of the before-after states in a view transition, images are replaced elements so, in theory, we should be able to use them as a sort of reduced test case for 3D animations. For example, flipping one image to reveal another on click looks like this:

    CodePen Embed Fallback

    It’s important to note that, for the animation to work properly, we need to set the perspective property on the image’s parent container (in our case, it’s the .scene element). Otherwise, the 3D transformation is merely flat. It sort of angles the element’s appearance:

    CodePen Embed Fallback

    In CSS, the parent’s persepective is applied to all its children, excluding itself:

    .scene {
      perspective: 1200px;
    
      .card { /* gets perspective */ }
    }

    What’s important here is the HTML structure. Specifically how the .scene container sits on top of the child .card elements, making the 3D effect come to life so the flip looks how it should:

    <div class="scene">
      <div class="card">
        <!-- Card Content Here -->
      </div>
    </div>

    Perhaps our keyframe animation to flip the .cards is something like this:

    @keyframes flipOut {
      from {
        transform: rotateY(0deg);
      }
      to {
        transform: rotateY(-90deg);
      }
    }

    Which we apply to the .cards like this:

    .card.flip-out {
      animation: flipOut 5.2s cubic-bezier(0.4, 0, 0.2, 1) forwards;
    }
    .card.flip-in {
      animation: flipOut 5.2s cubic-bezier(0.4, 0, 0.2, 1) forwards reverse;
    }

    …where the animates runs forwards when the .flip-out class is appended to the .card (courtesy of JavaScript watching for a click) and runs in reverse when the .flip-in class is appended.

    That’s the setup for how a cross-document view transition ought to work, too, right? If an image supports a 3D animation, then a view transitions snapshot should do the same. Let’s poke at that.

    Setting up the view transition

    First things first, we have to opt into view transitions on both pages with the @view-transition at-rule by setting the navigation descriptor to auto:

    @view-transition {
      navigation: auto;
    }

    If we were to do nothing else, then one page fades into another when navigating between the two. It’s the most basic of all cross-document view transitions.

    How do we customize things? We use the ::view-transition-old() and ::view-transition-new() pseudo-classes, where the former is the “old” snapshot and the latter is the “new” one. Like the .card elements we used in the last example, that’s where we set the keyframe animation:

    ::view-transition-old(root) {
      /* animation goes here */
    }
    
    ::view-transition-new(root) {
      /* animation goes here */
    }

    The root parameter tells the view transition to target the whole page and all the elements created (and not created) by the view transition’s default snapshot group.

    Here’s the problem

    Let’s say we want to apply that same 3D flip to the entire webpage, where the snapshot of the “old” page flips into the “new” page. Again, a 3D animation asks us for two things:

    1. The perspective property on the parent element so its children get that 3D effect
    2. An animation on the page for when the view transition happens

    But: What exactly do we set the perspective on, as in, what is the parent element here?

    Since view transitions take snapshots of the entire webpage, we might assume (logically) it would be the <html> element (or the :root), right? I mean, the DOM tree looks like this when a view transition is present:

    html
      ├─ ::view-transition
      │  ├─ ::view-transition-group(card)
      │  │  └─ ::view-transition-image-pair(card)
      │  │     ├─ ::view-transition-old(card)
      │  │     └─ ::view-transition-new(card)
      │  └─ ::view-transition-group(name)
      │     └─ ::view-transition-image-pair(name)
      │        ├─ ::view-transition-old(name)
      │        └─ ::view-transition-new(name)
      ├─ head
      └─ body
            └─ …

    So, the entire snapshot should be where we put the perspective. Right? Turns out, no.

    In fact, does nothing at all! You’re left with this instead of the beautiful 3D flip we were able to use on the cards earlier:

    GitHub Source and Live Demo

    Here’s the code I was working with:

    /* Cross-document View Transition opt-in */
    @view-transition {
      navigation: auto;
    }
    
    /* 3D flip: Old page flips away, new page flips in */
    @keyframes flip-out {
      0% {
        transform: rotateY(0deg);
        opacity: 1;
      }
      100% {
        transform: rotateY(-90deg);
        opacity: 0;
      }
    }
    
    @keyframes flip-in {
      0% {
        transform: rotateY(90deg);
        opacity: 0;
      }
      100% {
        transform: rotateY(0deg);
        opacity: 1;
      }
    }
    
    ::view-transition-old(root) {
      animation: flip-out 0.3s cubic-bezier(0.4, 0, 1, 1) forwards;
      transform-origin: center center;
    }
    
    ::view-transition-new(root) {
      animation: flip-in 0.3s cubic-bezier(0, 0, 0.6, 1) 0.3s backwards;
      transform-origin: center center;
    }

    Note: I didn’t reverse the animation here since we flip to -90deg and then from 90deg. Not exactly the same!

    And it doesn’t work, no matter if perspective is on html or :root:

    /* 👎 */
    html {
      perspective: 1100px;
    }
    
    /* 👎 */
    :root {
      perspective: 1100px;
    }

    I did some digging and discovered that perspective (and 3D transformations in general) is one of several CSS properties that would produce an unusual effect. (Leave it to Bramus to have the answer!)

    So… What do we do? Some ideas came to mind, but sadly failed:

    • I tried setting the perspective property on the body.
    • I tried setting perspective inside ::view-transition-group(root).
    • I tried setting perspective inside the ::view-transition pseudo.

    There’s actually a super simple workaround to this, and I can’t believe it took me this long to figure it out — don’t use perspective at all!

    The solution

    Short story: we have to use the perspective() function instead of the perspective property. And not inside any of the ::view-transition-* pseudos as you might expect, but inside the @keyframes animation:

    @keyframes flip-out {
      0% {
        transform: perspective(1100px) rotateY(0deg);
        opacity: 1;
      }
      100% {
        transform: perspective(1100px) rotateY(-90deg);
        opacity: 0;
      }
    }
    @keyframes flip-in {
      0% {
        transform: perspective(1100px) rotateY(90deg);
        opacity: 0;
      }
      100% {
        transform: perspective(1100px) rotateY(0deg);
        opacity: 1;
      }
    }

    This simple, but big change moves the scene from a flat meh to a beautiful ah yeah:

    GitHub Source and Live Demo

    Here’s why, apparently. The view transition pseudo-element tree is rendered outside the normal HTML flow. More specifically, the entire view transition tree is rendered above the DOM in its own layer. However, particularly for ::view-transition, I’m not too sure why this is the case, but my best guess would be that each view transition group automatically has its position and transform values overridden by the browser; hence, interfering with the perspective.

    The difference between perspective and perspective()? The perspective property is applied to the parent element, while perspective() is a transform property function applied directly to the element itself. And since the view transition pseudo tree does not have a true parent, we’ve gotta use perspective() since it doesn’t require a parent. Phew.

    To recap…

    Setting perspective on the html, :root, or any of the view transition pseudo-class won’t work. And if you have been struggling to find the solution, like I was, I think this little, but big perspective() change will solve that issue if you ever come across it. Take it from me, I battled with this for weeks till I came back today to rant about it and discovered a solution to it. A perk of writing!


    Why Isn’t My 3D View Transition Working? originally handwritten and published with love on CSS-Tricks. You should really get the newsletter as well.

    • ChatGPT probably isn’t conscious. But what if we’re wrong? Vox
    • He Profits Off Raw Milk That’s Making People Sick. The Government Isn’t Stopping Him. ProPublica
    • The US says ASML’s top chip tool may be in China. ASML says it isn’t. TechCrunch
    • The AI Scam Your Family Isn’t Ready For Cassie Kozyrkov
  • CSS-Tricks css-tricks.com css css-tricks frontend technology web-dev web-development 2026-06-04 13:14
    ↗

    We dive again into CSS Pie Charts! This time, Author Antoine Villepreux delivers semantic and flexible charts without a single line of JS. Another Stab at the Perfect CSS Pie Chart… Sans JavaScript! originally handwritten and published with love on CSS-Tricks. You should...

    Recently, Juan Diego Rodríguez published an excellent article exploring how far CSS can be pushed to build a semantic and customizable pie chart while keeping JavaScript to a minimum.

    Citing Juan himself:

    In this article, we’ll try making the perfect pie chart in CSS. That means avoiding as much JavaScript as possible while addressing major headaches that comes with handwriting pie charts.

    And it stated some goals that I want to go through again in order of priority:

    • This must be semantic! Meaning a screen reader should be able to understand the data shown in the pie chart.

    To my understanding, the original article’s solution reached that goal. Its semantic approach (labels in plain HTML + values as attributes reinjected into the DOM via pseudo-elements) is clean, expressive, and hopefully accessible.

    • This should be HTML-customizable! Once the CSS is done, we only have to change the markup to customize the pie chart.

    The original article reached that goal as well.

    • This should keep JavaScript to a minimum! No problem with JavaScript in general, it’s just more fun this way.

    The original article aimed to use as little JavaScript as possible, mainly for fun. I tend to disagree slightly. For me, it should not be just for fun, since…

    • JavaScript is there to deal with states and logic, and
    • CSS is there to style the markup.

    The initial “no JavaScript” constraint was meaningful to me. CSS should be powerful enough to let us style a pie chart. JavaScript should not be required. So, I decided to see whether there was a way to 100% get rid of it and, for fun, forked the article’s CodePen during a lunch break.

    I kept the original code as unchanged as possible, preserving its semantic approach and HTML-side customizability. If It Ain’t Broke, Don’t Fix It™.

    Coincidentally, this article came right after a recent short pen of mine toying with bar charts. So I was already in the mood for charts. But bar charts are far easier: each bar’s position or size does not depend on the others. A pie chart is a different beast: each slice’s position depends on the previous one. Luckily, this made it more of a fun challenge.

    But before diving into my take on pie charts, let’s see how these have been approached by other web developers.

    Prior Art

    I read many blogs, articles, and code examples from professional front-end developers, but I am not one myself, so I am not entirely certain of my ability to identify the most relevant and up-to-date prior art… Let’s try anyway.

    It is easy to find many JavaScript libraries dealing with charts. I have used them a lot in my work. However, due to our no-JavaScript constraint, we shall exclude them.

    I started looking for CSS-only pie charts, and one of the first libraries that pops up is Chart-CSS. It advertises semantic structure, HTML tags to display data, accessibility, and raw data inside the markup. It seems to be a very good library and does not use any JavaScript.

    Instead, it uses HTML tables, which, in my opinion and experience, makes total sense (most of the time, source data comes in a table). However, it does not solve the specific challenge of letting the user set only the values while having the start and end angles of each slice automatically computed. In this case, users still have to manually define them.

    There are also very good articles discussing charts or data visualization in general. To name a few:

    • Vitaly Friedman’s “2022 Guide to Accessible Front-End Components” is still relevant.
    • Sarah L. Fossheim wrote extensively about data visualization accessibility between 2020 and 2024

    They just have one small (but very important to us) drawback. While these resources are valuable in explaining how chart accessibility should work, they do not really address easy HTML “interface” nor pure CSS implementations.

    How I Tackled the Problem

    If you are still reading, I assume you are at least somewhat interested in my approach. Understandably, If you just want to see the code, here it is!

    CodePen Embed Fallback

    Initially, the reason JavaScript was required was that each slice needed to know the value of the previous one. However, due to how CSS property inheritance works, a child cannot know the state of another child. Despite knowing this, I first tried to determine whether there were niche or “voodoo” techniques that would allow me to keep the original HTML markup and attribute-based approach while removing JavaScript.

    I know that people like Roman Komarov can do incredible things with CSS, so I even considered exploring techniques involving property animations. But I clearly did not have the time to investigate that direction.

    I returned to the core issue: because of how CSS inheritance works, children cannot know the state of their siblings. I obviously needed a “surrounding entity” to handle this.

    In Juan’s post, that “entity” was JavaScript, which could loop through all children and compute the appropriate slice accumulations.

    const pieChartItems = document.querySelectorAll(".pie-chart li");
    
    let accum = 0;
    
    pieChartItems.forEach((item) => {
      item.style.setProperty("--accum", accum);
      accum += parseFloat(item.getAttribute("data-percentage"));
    });

    The JavaScript code sets an --accum value for each slice, which holds the percentage values of all charts prior to it. Without it, we wouldn’t know where to position each slice and its corresponding label.

    In HTML/CSS, that entity exists too: the classic parent element. Therefore, my solution was to move the percentage values to the parent.

    First, let’s remember what the original markup for the pie chart looked like this:

    <ul class="pie-chart">
      <li data-percentage-1="10">Apple</li>
      <li data-percentage-2="30">Banana</li>
      <li data-percentage-3="20">Orange</li>
      <li data-percentage-4="40">Strawberry</li>
    </ul>

    While the version we’ll be using looks like this:

    <ul class="pie-chart" data-percentage-1="10" data-percentage-2="30" data-percentage-3="20" data-percentage-4="40">
      <li>Apple</li>
      <li>Banana</li>
      <li>Orange</li>
      <li>Strawberry</li>
    </ul>

    We’ve moved all values to the parent <ul> and given each item a dedicated name — effectively indexing them.

    I had previously experimented with this kind of “indexing” CSS workaround, for example, to compensate for the lack of sibling-index() and sibling-count() functions to generate random numbers. I knew this was the right direction and that the rest would follow logically on the CSS side.

    Spoiler: sibling-index() and sibling-count() are becoming Baseline soon!

    It may look like duplication since we didn’t add anything but rather moved the attributes. However, this slight change allows us to manage all labels and values from the parent in CSS. What’s best, we still keep all attributes close together. And while you may say that this won’t scale as well, if we have data with tons of entries, then a pie chart is rarely the best choice to show it.

    Optionally, we could add data-label attributes to the labels just to pair labels and values visually.

    <ul class="pie-chart" data-percentage-1="10" data-percentage-2="30" data-percentage-3="20" data-percentage-4="40">
      <!-- Optional data-label attributes: just visual hints-->
      <li data-label-1>Apple</li>
      <li data-label-2>Banana</li>
      <li data-label-3>Orange</li>
      <li data-label-4>Strawberry</li>
    </ul>

    Now let’s examine the CSS. The implementation requires two sets of some repetitive but straightforward CSS rules.

    Firstly, we’ll need to pass down each percentage to its corresponding slice. To do so, we use Juan’s and get the data-percentage attributes into CSS through the upgraded attr() function. In parallel, we’ll assign them to the corresponding slice using the nth-child() selector.

    .pie-chart {
       /* We write one for each slice we think we'll need */
      --p-100-1: attr(data-percentage-1 type(<number>)); :nth-child(1) { --p-100: var(--p-100-1) }
      --p-100-2: attr(data-percentage-2 type(<number>)); :nth-child(2) { --p-100: var(--p-100-2) }
      --p-100-3: attr(data-percentage-3 type(<number>)); :nth-child(3) { --p-100: var(--p-100-3) }
      --p-100-4: attr(data-percentage-4 type(<number>)); :nth-child(4) { --p-100: var(--p-100-4) }
       /*...*/
    }

    For that kind of repetitive/incremental code, I keep it as a one-liner without carriage return. IMHO it’s a very acceptable exception to common formatting rules as it prevents typos by easing scan-ability of and also eases further iterations (e.g., adding support for more slices). But your mileage may vary.

    Let’s look a little closer at what’s going on here. At the level of the whole pie, we access the percentages for each slice through their index and store them in a corresponding CSS variable, so the fourth element gets --p-100-4, the fifth element gets --p-100-5, and so on:

    --p-100-4: attr(data-percentage-4 type(<number>));

    Next, we pass each a --p-100 variable that’s local to each slice.

    :nth-child(4) {
      --p-100: var(--p-100-4);
    }

    We now have all these slice values accessible at two levels:

    • On the pie, via indexed variables: --p-100-1, --p-100-2, --p-100-3
    • On each slice, via the --p-100 variable

    Now, we’ll need to calculate the corresponding --accum value, which is the sum of the values of all previous slices. To do so, we’ll have to progressively sum each percentage after each slice, then assign the value to the slice using nth-child() again.

    .pie-chart {
      /* ... */
      --accum-1: 0;                                     :nth-child(1) { --accum: var(--accum-1) }
      --accum-2: calc(var(--accum-1) + var(--p-100-1)); :nth-child(2) { --accum: var(--accum-2) }
      --accum-3: calc(var(--accum-2) + var(--p-100-2)); :nth-child(3) { --accum: var(--accum-3) }
      --accum-4: calc(var(--accum-3) + var(--p-100-3)); :nth-child(4) { --accum: var(--accum-4) }
      /*...*/
    }

    Again, we first work at the pie level, where we compute one dedicated variable per slice. The first slice is a special case: there is no previous slice, so the accumulation is 0. While in the rest, the accumulation for the slice n is the accumulation of the slices before n−1 plus the value of the slice n−1.

    --accum-4: calc(var(--accum-3) + var(--p-100-3));

    The fourth element gets --accum-4, the fifth element gets --accum-5, and so on. Just as the percentages, at the level of each slice, we assign them to the local variable --accum.

    :nth-child(4) {
      --accum: var(--accum-4);
    }

    Once again, we have all these slice accumulation values accessible at two levels:

    • On the pie, via indexed variables: --accum-1, --accum-2, --accum-3
    • On each slice, via the --accum variable

    I hope future native CSS features (perhaps @function?) will prevent us from having to resort to such repetitive code. In the meantime, this can be simplified with a CSS preprocessor (Sass, Less).

    While forking the original Pen, some questions popped out in my mind — that I did not actually explore — to keep the original code as unchanged as possible:

    • What about using <label> and <meter> for labels and values?
    • What about using a <table> (since charts are often extracted from tables with rows like [label, value])?

    Note About Accessibility

    In my fork, I handled accessibility the same way Juan did, but with one slight modification: I used counter-reset / counter() instead of attr() to assign the percentages to the content property. This should work just as good as attr(), but let’s make sure it is still screenreader-friendly:

    Another thing I thought of changing was the label elements inside each <li>. In the original article, Juan uses a <strong> element, while I opted for <span> instead. However, I think it may be totally acceptable to use the <label> itself. We normally think of them as being bounded inside <form> elements, but the spec says that we could expect to use them in contexts “where phrasing content is expected.” So I could not find any obligation to use them only in the context of forms.

    Default Colors

    Juan’s article also called upon some improvements, which I tried to address in my fork:

    • data-color can be omitted, and colors are then generated.
    • Colors can be defined either on the parent or on the children (user’s choice; both are supported).

    This translates to the next snippet for each slice:

    .pie-chart li {
      --color: attr(data-color type(<color>));
      --bg-color: var(--color, hsl(calc(360deg * sibling-index() / sibling-count()) 90% 40%));
    }

    I refrained from using sibling-index() and sibling-count() in the main part, since they aren’t Baseline (yet, but soon!), but I couldn’t hold myself back since calculating the color hue is so much fancier with them. These functions really allow some magic!

    Still, here is my “CSS-only polyfill”; repetitive (yet simple) code:

    .pie-chart {
      :has(:nth-child(1)) { --sibling-count: 1 } :nth-child(1) { --sibling-index: 1; }
      :has(:nth-child(2)) { --sibling-count: 2 } :nth-child(2) { --sibling-index: 2; }
      :has(:nth-child(3)) { --sibling-count: 3 } :nth-child(3) { --sibling-index: 3; }
      :has(:nth-child(4)) { --sibling-count: 4 } :nth-child(4) { --sibling-index: 4; }
      /* ... */
    }

    More Chart Types?

    We now have a common foundation for other chart types. As a proof of concept, I implemented a bar chart mode in my fork.

    CodePen Embed Fallback

    A Web Component, Perhaps?

    In a way, we already have a web component here — one without JavaScript, using light DOM.

    And to me <pie-char attributes...> is not fundamentally different that either <div class="pie chart" attributes...> or <div pie chart attributes...>. I can see value in this approach when considering progressive enhancement, though.

    For example, a chart that refreshes automatically and fetches live data. But that would require JavaScript — which we are deliberately avoiding today.


    Another Stab at the Perfect CSS Pie Chart… Sans JavaScript! originally handwritten and published with love on CSS-Tricks. You should really get the newsletter as well.

    • Want to start stargazing? Here's why June is the perfect time for newcomers Space.com
    • The Perfect X-Wing Doesn't Exi— Tested
    • What's the perfect encoding? How do you know? 3Blue1Brown
    • Summarise Anything Like a Pro in ChatGPT (Master the Perfect ChatGPT Prompts) Simon Sez IT
  • End of feed
Maibook — your private personalized AI community
  • rcanand.com
  • mlaillc.com
  • @rcanand (X)
  • LinkedIn
  • Feedback
  • Credits