> All in One 586: December 2024

Ads

Tuesday, December 31, 2024

Snow Showers Early today!



With a high of F and a low of 17F. Currently, it's 26F and Cloudy outside.

Current wind speeds: 4 from the Northwest

Pollen: 0

Sunrise: December 31, 2024 at 08:11PM

Sunset: January 1, 2025 at 05:39AM

UV index: 0

Humidity: 63%

via https://ift.tt/PAxZzQt

January 1, 2025 at 10:02AM

Monday, December 30, 2024

Partly Cloudy/Wind today!



With a high of F and a low of 18F. Currently, it's 30F and Clear/Wind outside.

Current wind speeds: 20 from the Northwest

Pollen: 0

Sunrise: December 30, 2024 at 08:10PM

Sunset: December 31, 2024 at 05:38AM

UV index: 0

Humidity: 75%

via https://ift.tt/inf6dkZ

December 31, 2024 at 10:02AM

Thank You (2024 Edition)

Sunday, December 29, 2024

Partly Cloudy/Wind today!



With a high of F and a low of 35F. Currently, it's 38F and Clear outside.

Current wind speeds: 14 from the South

Pollen: 0

Sunrise: December 29, 2024 at 08:10PM

Sunset: December 30, 2024 at 05:37AM

UV index: 0

Humidity: 68%

via https://ift.tt/0tAu5cl

December 30, 2024 at 10:02AM

Saturday, December 28, 2024

Partly Cloudy today!



With a high of F and a low of 27F. Currently, it's 35F and Partly Cloudy outside.

Current wind speeds: 12 from the West

Pollen: 0

Sunrise: December 28, 2024 at 08:10PM

Sunset: December 29, 2024 at 05:36AM

UV index: 0

Humidity: 60%

via https://ift.tt/3dJCYXG

December 29, 2024 at 10:02AM

Friday, December 27, 2024

Partly Cloudy today!



With a high of F and a low of 27F. Currently, it's 32F and Clear outside.

Current wind speeds: 6 from the South

Pollen: 0

Sunrise: December 27, 2024 at 08:10PM

Sunset: December 28, 2024 at 05:36AM

UV index: 0

Humidity: 52%

via https://ift.tt/fceVuwd

December 28, 2024 at 10:02AM

Thursday, December 26, 2024

Clouds Early/Clearing Late today!



With a high of F and a low of 28F. Currently, it's 39F and Partly Cloudy outside.

Current wind speeds: 9 from the South

Pollen: 0

Sunrise: December 26, 2024 at 08:09PM

Sunset: December 27, 2024 at 05:35AM

UV index: 0

Humidity: 72%

via https://ift.tt/EHROrxW

December 27, 2024 at 10:02AM

Wednesday, December 25, 2024

Partly Cloudy today!



With a high of F and a low of 24F. Currently, it's 30F and Mostly Cloudy outside.

Current wind speeds: 5 from the Northwest

Pollen: 0

Sunrise: December 25, 2024 at 08:09PM

Sunset: December 26, 2024 at 05:34AM

UV index: 0

Humidity: 100%

via https://ift.tt/4UogcAD

December 26, 2024 at 10:02AM

Tuesday, December 24, 2024

Mostly Cloudy today!



With a high of F and a low of 30F. Currently, it's 34F and Clear outside.

Current wind speeds: 12 from the South

Pollen: 0

Sunrise: December 24, 2024 at 08:09PM

Sunset: December 25, 2024 at 05:34AM

UV index: 0

Humidity: 76%

via https://ift.tt/Tsa581w

December 25, 2024 at 10:02AM

Monday, December 23, 2024

Light Rain Early today!



With a high of F and a low of 31F. Currently, it's 40F and Showers in the Vicinity outside.

Current wind speeds: 6 from the Southwest

Pollen: 0

Sunrise: December 23, 2024 at 08:08PM

Sunset: December 24, 2024 at 05:33AM

UV index: 0

Humidity: 66%

via https://ift.tt/M82uPsC

December 24, 2024 at 10:02AM

A CSS Wishlist for 2025

2024 has been one of the greatest years for CSS: cross-document view transitions, scroll-driven animations, anchor positioning, animate to height: auto, and many others. It seems out of touch to ask, but what else do we want from CSS? Well, many things!

We put our heads together and came up with a few ideas… including several of yours.

Geoff’s wishlist

I’m of the mind that we already have a BUNCH of wonderful CSS goodies these days. We have so many wonderful — and new! — things that I’m still wrapping my head around many of them.

But! There’s always room for one more good thing, right? Or maybe room for four new things. If I could ask for any new CSS features, these are the ones I’d go for.

1. A conditional if() statement

It’s coming! Or it’s already here if you consider that the CSS Working Group (CSSWG) resolved to add an if() conditional to the CSS Values Module Level 5 specification. That’s a big step forward, even if it takes a year or two (or more?!) to get a formal definition and make its way into browsers.

My understanding about if() is that it’s a key component for achieving Container Style Queries, which is what I ultimately want from this. Being able to apply styles conditionally based on the styles of another element is the white whale of CSS, so to speak. We can already style an element based on what other elements it :has() so this would expand that magic to include conditional styles as well.

2. CSS mixins

This is more of a “nice-to-have” feature because I feel its squarely in CSS Preprocessor Territory and believe it’s nice to have some tooling for light abstractions, such as writing functions or mixins in CSS. But I certainly wouldn’t say “no” to having mixins baked right into CSS if someone was offering it to me. That might be the straw that breaks the CSS preprocessor back and allows me to write plain CSS 100% of the time because right now I tend to reach for Sass when I need a mixin or function.

I wrote up a bunch of notes about the mixins proposal and its initial draft in the specifications to give you an idea of why I’d want this feature.

3. // inline comments

Yes, please! It’s a minor developer convenience that brings CSS up to par with writing comments in other languages. I’m pretty sure that writing JavaScript comments in my CSS should be in my list of dumbest CSS mistakes (even if I didn’t put it in there).

4. font-size: fit

I just hate doing math, alright?! Sometimes I just want a word or short heading sized to the container it’s in. We can use things like clamp() for fluid typesetting, but again, that’s math I can’t be bothered with. You might think there’s a possible solution with Container Queries and using container query units for the font-size but that doesn’t work any better than viewport units.

Ryan’s wishlist

I’m just a simple, small-town CSS developer, and I’m quite satisfied with all the new features coming to browsers over the past few years, what more could I ask for?

5. Anchor positioning in more browsers!

I don’t need anymore convincing on CSS anchor positioning, I’m sold! After spending much of the month of November learning how it works, I went into December knowing I won’t really get to use it for a while.

As we close out 2024, only Chromium-based browsers have support, and fallbacks and progressive enhancements are not easy, unfortunately. There is a polyfill available (which is awesome), however, that does mean adding another chunk of JavaScript, contrasting what anchor positioning solves.

I’m patient though, I waited a long time for :has to come to browsers, which has been “newly available” in Baseline for a year now (can you believe it?).

6. Promoting elements to the #top-layer without popover?

I like anchor positioning, I like popovers, and they go really well together!

The neat thing with popovers is how they appear in the #top-layer, so you get to avoid stacking issues related to z-index. This is probably all most would need with it, but having some other way to move an element there would be interesting. Also, now that I know that the #top-layer exists, I want to do more with it — I want to know what’s up there. What’s really going on?

Well, I probably should have started at the spec. As it turns out, the CSS Position Layout Module Level 4 draft talks about the #top-layer, what it’s useful for, and ways to approach styling elements contained within it. Interestingly, the #top-layer is controlled by the user agent and seems to be a byproduct of the Fullscreen API.

Dialogs and popovers are the way to go for now but, optimistically speaking, these features existing might mean it’s possible to promote elements to the #top-layer in future ways. This very well may be a coyote/roadrunner-type situation, as I’m not quite sure what I’d do with it once I get it.

Personally speaking, Cascade Layers have changed how I write CSS. One thing I think would be ace is if we could include a layer attribute on a <link> tag. Imagine being able to include a CSS reset in your project like:

<link rel="stylesheet" href="https://cdn.com/some/reset.css" layer="reset">

Or, depending on the page visited, dynamically add parts of CSS, blended into your cascade layers:

<!-- 
Global styles with layers defined, such as:
 @layer reset, typography, components, utilities;
-->
<link rel="stylesheet" href="/styles/main.css"> 

<!-- Add only to pages using card components  -->
<link rel="stylesheet" href="/components/card.css" layer="components">

This feature was proposed over on the CSSWG’s repo, and like most things in life: it’s complicated.

Browsers are especially finicky with attributes they don’t know, plus definite concerns around handling fallbacks. The topic was also brought over to the W3C Technical Architecture Group (TAG) for discussion as well, so there’s still hope!

Juandi’s Wishlist

I must admit this, I wasn’t around when the web was wild and people had hit counters. In fact, I think I am pretty young compared to your average web connoisseur. While I do know how to make a layout using float (the first web course I picked up was pretty outdated), I didn’t have to suffer long before using things like Flexbox or CSS Grid and never grinded my teeth against IE and browser support.

So, the following wishes may seem like petty requests compared to the really necessary features the web needed in the past — or even some in the present. Regardless, here are my three petty requests I would wish to see in 2025:

8. Get the children count and index as an integer

This is one of those things that you swear it should already be possible with just CSS. The situation is the following: I find myself wanting to know the index of an element between its siblings or the total number of children. I can’t use the counter() function since sometimes I need an integer instead of a string. The current approach is either hardcoding an index on the HTML:

<ul>
  <li style="--index: 0">Milk</li>
  <li style="--index: 1">Eggs</li>
  <li style="--index: 2">Cheese</li>
</ul>

Or alternatively, write each index in CSS:

li:nth-child(1) { --index: 0; }
li:nth-child(2) { --index: 1; }
li:nth-child(3) { --index: 2; }

Either way, I always leave with the feeling that it should be easier to reference this number; the browser already has this info, it’s just a matter of exposing it to authors. It would make prettier and cleaner code for staggering animations, or simply changing the styles based on the total count.

Luckily, there is a already proposal in Working Draft for sibling-count() and sibling-index() functions. While the syntax may change, I do hope to hear more about them in 2025.

ul > li {
  background-color: hsl(sibling-count() 50% 50%);
}

ul > li {
  transition-delay: calc(sibling-index() * 500ms);
}

9. A way to balance flex-wrap

I’m stealing this one from Adam Argyle, but I do wish for a better way to balance flex-wrap layouts. When elements wrap one by one as their container shrinks, they either are left alone with empty space (which I don’t dislike) or grow to fill it (which hurts my soul):

Flex Wrap leaving empty space or filling it completely

I wish for a more native way of balancing wrapping elements:

Flex wrap balancing elements

It’s definitely annoying.

10. An easier way to read/research CSSWG discussions

I am a big fan of the CSSWG and everything they do, so I spent a lot of time reading their working drafts, GitHub issues, or notes about their meetings. However, as much as I love jumping from link to link in their GitHub, it can be hard to find all the related issues to a specific discussion.

I think this raises the barrier of entry to giving your opinion on some topics. If you want to participate in an issue, you should have the big picture of all the discussion (what has been said, why some things don’t work, others to consider, etc) but it’s usually scattered across several issues or meetings. While issues can be lengthy, that isn’t the problem (I love reading them), but rather not knowing part of a discussion existed somewhere in the first place.

So, while it isn’t directly a CSS wish, I wish there was an easier way to get the full picture of the discussion before jumping in.

What’s on your wishlist?

We asked! You answered! Here are a few choice selections from the crowd:

  • Rotate direct background-images, like background-rotate: 180deg
  • CSS random(), with params for range, spread, and type
  • A CSS anchor position mode that allows targeting the mouse cursor, pointer, or touch point positions
  • A string selector to query a certain word in a block of text and apply styling every time that word occurs
  • A native .visually-hidden class.
  • position: sticky with a :stuck pseudo

Wishing you a great 2025…

CSS-Tricks trajectory hasn’t been the most smooth these last years, so our biggest wish for 2025 is to keep writing and sparking discussions about the web. Happy 2025!


A CSS Wishlist for 2025 originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.



from CSS-Tricks https://ift.tt/Q1Z07Yv
via IFTTT

Sunday, December 22, 2024

Showers Early today!



With a high of F and a low of 31F. Currently, it's 46F and Fair outside.

Current wind speeds: 6 from the Northwest

Pollen: 0

Sunrise: December 22, 2024 at 08:08PM

Sunset: December 23, 2024 at 05:32AM

UV index: 0

Humidity: 38%

via https://ift.tt/ryI4CXQ

December 23, 2024 at 10:02AM

Saturday, December 21, 2024

Clear today!



With a high of F and a low of 32F. Currently, it's 38F and Clear outside.

Current wind speeds: 13 from the Southwest

Pollen: 0

Sunrise: December 21, 2024 at 08:07PM

Sunset: December 22, 2024 at 05:32AM

UV index: 0

Humidity: 42%

via https://ift.tt/8txE5ih

December 22, 2024 at 10:02AM

Friday, December 20, 2024

Mostly Clear today!



With a high of F and a low of 27F. Currently, it's 34F and Clear outside.

Current wind speeds: 10 from the West

Pollen: 0

Sunrise: December 20, 2024 at 08:07PM

Sunset: December 21, 2024 at 05:31AM

UV index: 0

Humidity: 59%

via https://ift.tt/ZXyt1el

December 21, 2024 at 10:02AM

The Little Triangle in the Tooltip

Tooltips are like homemade food: everyone uses them and everyone has their own recipe to make them. If you don’t remember a particular recipe, you will search for one, follow it, and go on with your day. This “many ways to do the same thing” concept is general to web development and programming (and life!), but it’s something that especially rings true with tooltips. There isn’t a specialized way to make them — and at this point, it isn’t needed — so people come up with different ways to fill those gaps.

Today, I want to focus on just one step of the recipe, which due to lack of a better name, I’ll just call the little triangle in the tooltip. It’s one of those things that receives minimal attention (admittedly, I didn’t know much before writing this) but it amazes you how many ways there are to make them. Let’s start with the simplest and make our way up to the not-so-simple.

Ideally, the tooltip is just one element. We want to avoid polluting our markup just for that little triangle:

<span class="tooltip">I am a tooltip</span>

Clever border

Before running, we have to learn to walk. And before connecting that little triangle we have to learn to make a triangle. Maybe the most widespread recipe for a triangle is the border trick, one that can be found in Stack Overflow issues from 2010 or even here by Chris in 2016.

In a nutshell, borders meet each other at 45° angles, so if an element has a border but no width and height, the borders will make four perfect triangles. What’s left is to set three border colors to transparent and only one triangle will show! You can find an animated version on this CodePen by Chris Coyier

Usually, our little triangle will be a pseudo-element of the tooltip, so we need to set its dimensions to 0px (which is something ::before and ::after already do) and only set one of the borders to a solid color. We can control the size of the triangle base by making the other borders wider, and the height by making the visible border larger.

.tooltip {
  &::before {
    content: "";

    border-width: var(--triangle-base);
    border-style: solid;
    border-color: transparent;

    border-top: var(--triangle-height) solid red;
  }
}

Attaching the triangle to its tooltip is an art in itself, so I am going with the basics and setting the little triangle’s position to absolute and the .tooltip to relative, then playing with its inset properties to place it where we want. The only thing to notice is that we will have to translate the little triangle to account for its width, -50% if we are setting its position with the left property, and 50% if we are using right.

.tooltip {
  position: relative;

  &::before {
    /* ... */
    position: absolute;
    top: var(--triangle-top);
    left: var(--triangle-left);

    transform: translateX(-50%);
  }
}

However, we could even use the new Anchor Positioning properties for the task. Whichever method you choose, we should now have that little triangle attached to the tooltip:

Rotated square

One drawback from that last example is that we are blocking the border property so that if we need it for something else, we are out of luck. However, there is another old-school method to make that little triangle: we rotate a square by 45° degrees and hide half of it behind the tooltip’s body. This way, only the corner shows in the shape of a triangle. We can make the square out of a pseudo-element:

.tooltip {
  &::before {
    content: "";

    display: block;
    height: var(--triangle-size);
    width: var(--triangle-size);

    background-color: red;
  }
}

Then, position it behind the tooltip’s body. In this case, such that only one-half shows. Since the square is rotated, the transformation will be on both axes.

.tooltip {
  position: relative;

  &::before {
    /* ... */
    position: absolute;
    top: 75%;
    left: 50%;
    z-index: -1; /* So it's behind the tooltip's body */

    transform: translateX(-50%);
    transform: rotate(45deg) translateY(25%) translateX(-50%);
  }
}

I also found that this method works better with Anchor Positioning since we don’t have to change the little triangle’s styles whenever we move it around. Unlike the border method, in which the visible border changes depending on the direction.

Trimming the square with clip-path

Although I didn’t mention it before, you may have noticed some problems with that last approach. First off, it isn’t exactly a triangle, so it isn’t the most bulletproof take; if the tooltip is too short, the square could sneak out on the top, and moving the false triangle to the sides reveals its true square nature. We can solve both issues using the clip-path property.

The clip-path property allows us to select a region of an element to display while clipping the rest. It works by providing the path we want to trim through, and since we want a triangle out of a square, we can use the polygon() function. It takes points in the element and trims through them in straight lines. The points can be written as percentages from the origin (i.e., top-left corner), and in this case, we want to trim through three points 0% 0% (top-left corner), 100% 0% (top-right corner) and 50% 100% (bottom-center point).

Path to trim an isosceles triangle

So, the clip-path value would be the polygon() function with those three points in a comma-separated list:

.tooltip {
  &::before {
    content: "";

    width: var(--triangle-base);
    height: var(--triangle-height);

    clip-path: polygon(0% 0%, 100% 0%, 50% 100%);
    transform: translate(-50%);

    background-color: red;
  }
}

This time, we will set the top and left properties using CSS variables, which will come in handy later.

.tooltip {
  position: relative;

  &::before {
    /* ... */
    position: absolute;
    top: var(--triangle-top); /* 100% */
    left: var(--triangle-left); /* 50% */

    transform: translate(-50%);
  }
}

And now we should have a true little triangle attached to the tooltip:

However, if we take the little triangle to the far end of any side, we can still see how it slips out of the tooltip’s body. Luckily, the clip-path property gives us better control of the triangle’s shape. In this case, we can change the points the trim goes through depending on the horizontal position of the little triangle. For the top-left corner, we want its horizontal value to approach 50% when the tooltip’s position approaches 0%, while the top-right corner should approach 50% when the tooltip position approaches 100%.

Path to trim right triangles

The following min() + max() combo does exactly that:

.tooltip {
  clip-path: polygon(
    max(50% - var(--triangle-left), 0%) 0,
    min(150% - var(--triangle-left), 100%) 0%,
    50% 100%
  );
}

The calc() function isn’t necessary inside math functions like min() and max().

Try to move the tooltip around and see how its shape changes depending on where it is on the horizontal axis:

Using the border-image property

It may look like our last little triangle is the ultimate triangle. However, imagine a situation where you have already used both pseudo-elements and can’t spare one for the little triangle, or simply put, you want a more elegant way of doing it without any pseudo-elements. The task may seem impossible, but we can use two properties for the job: the already-seen clip-path and the border-image property.

Using the clip-path property, we could trim the shape of a tooltip — with the little triangle included! — directly out of the element. The problem is that the element’s background isn’t big enough to account for the little triangle. However, we can use the border-image property to make an overgrown background. The syntax is a bit complex, so I recommend reading this full dive into border-image by Temani Afif. In short, it allows us to use an image or CSS gradient as the border of an element. In this case, we are making a border as wide as the triangle height and with a solid color.

.tooltip {
  border-image: fill 0 // var(--triangle-height) conic-gradient(red 0 0);;
}

The trim this time will be a little more complex, since we will also trim the little triangle, so more points are needed. Exactly, the following seven points:

The clip-path trim the shape of a tooltip body and little triangle

This translates to the following clip-path value:

.tooltip {
  /* ... */
  clip-path: polygon(
    0% 100%,
    0% 0%,
    100% 0%,
    100% 100%,
    calc(50% + var(--triangle-base) / 2) 100%,
    50% calc(100% + var(--triangle-height)),
    calc(50% - var(--triangle-base) / 2) 100%
  );
}

We can turn it smart by also capping the little triangle bottom point whenever it gets past any side of the tooltip:

.tooltip {
  /* ... */
  clip-path: polygon(
    0% 100%,
    0% 0%,
    100% 0%,
    100% 100%,
    min(var(--triangle-left) + var(--triangle-base) / 2, 100%) 100%,
    var(--triangle-left) calc(100% + var(--triangle-height)),
    max(var(--triangle-left) - var(--triangle-base) / 2, 0%) 100%
  ;
}

And now we have our final little triangle of the tooltip, one that is part of the main body and only uses one element!

More information


The Little Triangle in the Tooltip originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.



from CSS-Tricks https://ift.tt/rXc0juF
via IFTTT

Thursday, December 19, 2024

Clear today!



With a high of F and a low of 23F. Currently, it's 27F and Clear outside.

Current wind speeds: 4 from the Northwest

Pollen: 0

Sunrise: December 19, 2024 at 08:06PM

Sunset: December 20, 2024 at 05:31AM

UV index: 0

Humidity: 62%

via https://ift.tt/1uwLdpb

December 20, 2024 at 10:02AM

Wednesday, December 18, 2024

Partly Cloudy today!



With a high of F and a low of 29F. Currently, it's 38F and Clear outside.

Current wind speeds: 16 from the Southwest

Pollen: 0

Sunrise: December 18, 2024 at 08:06PM

Sunset: December 19, 2024 at 05:31AM

UV index: 0

Humidity: 57%

via https://ift.tt/pWrSGK1

December 19, 2024 at 10:02AM

How to Create Multi-Step Forms With Vanilla JavaScript and CSS

Multi-step forms are a good choice when your form is large and has many controls. No one wants to scroll through a super-long form on a mobile device. By grouping controls on a screen-by-screen basis, we can improve the experience of filling out long, complex forms.

But when was the last time you developed a multi-step form? Does that even sound fun to you? There’s so much to think about and so many moving pieces that need to be managed that I wouldn’t blame you for resorting to a form library or even some type of form widget that handles it all for you.

But doing it by hand can be a good exercise and a great way to polish the basics. I’ll show you how I built my first multi-step form, and I hope you’ll not only see how approachable it can be but maybe even spot areas to make my work even better.

We’ll walk through the structure together. We’ll build a job application, which I think many of us can relate to these recent days. I’ll scaffold the baseline HTML, CSS, and JavaScript first, and then we’ll look at considerations for accessibility and validation.

I’ve created a GitHub repo for the final code if you want to refer to it along the way.

The structure of a multi-step form

Our job application form has four sections, the last of which is a summary view, where we show the user all their answers before they submit them. To achieve this, we divide the HTML into four sections, each identified with an ID, and add navigation at the bottom of the page. I’ll give you that baseline HTML in the next section.

Navigating the user to move through sections means we’ll also include a visual indicator for what step they are at and how many steps are left. This indicator can be a simple dynamic text that updates according to the active step or a fancier progress bar type of indicator. We’ll do the former to keep things simple and focused on the multi-step nature of the form.,

The structure and basic styles

We’ll focus more on the logic, but I will provide the code snippets and a link to the complete code at the end.

Let’s start by creating a folder to hold our pages. Then, create an index.html file and paste the following into it:

Open HTML
<form id="myForm">
  <section class="group-one" id="one">
    <div class="form-group">
      <div class="form-control">
        <label for="name">Name <span style="color: red;">*</span></label>
        <input type="text" id="name" name="name" placeholder="Enter your name">
      </div>

      <div class="form-control">
        <label for="idNum">ID number <span style="color: red;">*</span></label>
        <input type="number" id="idNum" name="idNum" placeholder="Enter your ID number">
      </div>
    </div>

    <div class="form-group">
      <div class="form-control">
        <label for="email">Email <span style="color: red;">*</span></label>
        <input type="email" id="email" name="email" placeholder="Enter your email">
      </div>

      <div class="form-control">
        <label for="birthdate">Date of Birth <span style="color: red;">*</span></label>
        <input type="date" id="birthdate" name="birthdate" max="2006-10-01" min="1924-01-01">
      </div>
    </div>
  </section>

  <section class="group-two" id="two">
    <div class="form-control">
      <label for="document">Upload CV <span style="color: red;">*</span></label>
      <input type="file" name="document" id="document">
    </div>

    <div class="form-control">
      <label for="department">Department <span style="color: red;">*</span></label>
      <select id="department" name="department">
        <option value="">Select a department</option>
        <option value="hr">Human Resources</option>
        <option value="it">Information Technology</option>
        <option value="finance">Finance</option>
      </select>
    </div>
  </section>

  <section class="group-three" id="three">
    <div class="form-control">
      <label for="skills">Skills (Optional)</label>
      <textarea id="skills" name="skills" rows="4" placeholder="Enter your skills"></textarea>
    </div>

    <div class="form-control">
      <input type="checkbox" name="terms" id="terms">
      <label for="terms">I agree to the terms and conditions <span style="color: red;">*</span></label>
    </div>

    <button id="btn" type="submit">Confirm and Submit</button>
  </section>
  
  <div class="arrows">
    <button type="button" id="navLeft">Previous</button>
    <span id="stepInfo"></span>
    <button type="button" id="navRight">Next</button>
  </div>
</form>

<script src="script.js"></script>

Looking at the code, you can see three sections and the navigation group. The sections contain form inputs and no native form validation. This is to give us better control of displaying the error messages because native form validation is only triggered when you click the submit button.

Next, create a styles.css file and paste this into it:

Open base styles
:root {
  --primary-color: #8c852a;
  --secondary-color: #858034;
}

body {
  font-family: sans-serif;
  line-height: 1.4;
  margin: 0 auto;
  padding: 20px;
  background-color: #f4f4f4;
  max-width: 600px;
}

h1 {
  text-align: center;
}

form {
  background: #fff;
  padding: 40px;
  border-radius: 5px;
  box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
  display: flex;
  flex-direction: column;
}

.form-group {
  display: flex;
  gap: 7%;

}

.form-group > div {
  width: 100%;
}

input:not([type="checkbox"]),
select,
textarea {
  width: 100%;
  padding: 8px;
  border: 1px solid #ddd;
  border-radius: 4px;
}

.form-control {
  margin-bottom: 15px;
}

button {
  display: block;
  width: 100%;
  padding: 10px;
  color: white;
  background-color: var(--primary-color);
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-size: 16px;

}

button:hover {
  background-color: var(--secondary-color);
}

.group-two, .group-three {
  display: none;
}

.arrows {
  display: flex;
  justify-content: space-between
  align-items: center;
  margin-top: 10px;
}

#navLeft, #navRight {
  width: fit-content;
}

@media screen and (max-width: 600px) {
  .form-group {
    flex-direction: column;
  }
}

Open up the HTML file in the browser, and you should get something like the two-column layout in the following screenshot, complete with the current page indicator and navigation.

Page one of a three-page form with four fields in a two-column layout

Adding functionality with vanilla JavaScript

Now, create a script.js file in the same directory as the HTML and CSS files and paste the following JavaScript into it:

Open base scripts
const stepInfo = document.getElementById("stepInfo");
const navLeft = document.getElementById("navLeft");
const navRight = document.getElementById("navRight");
const form = document.getElementById("myForm");
const formSteps = ["one", "two", "three"];

let currentStep = 0;

function updateStepVisibility() {
  formSteps.forEach((step) => {
    document.getElementById(step).style.display = "none";
  });

  document.getElementById(formSteps[currentStep]).style.display = "block";
  stepInfo.textContent = `Step ${currentStep + 1} of ${formSteps.length}`;
  navLeft.style.display = currentStep === 0 ? "none" : "block";
  navRight.style.display =
  currentStep === formSteps.length - 1 ? "none" : "block";
}

document.addEventListener("DOMContentLoaded", () => {
  navLeft.style.display = "none";
  updateStepVisibility();
  navRight.addEventListener("click", () => {
    if (currentStep < formSteps.length - 1) {
      currentStep++;
      updateStepVisibility();
    }
  });

  navLeft.addEventListener("click", () => {
    if (currentStep > 0) {
      currentStep--;
      updateStepVisibility();
    }
  });
});

This script defines a method that shows and hides the section depending on the formStep values that correspond to the IDs of the form sections. It updates stepInfo with the current active section of the form. This dynamic text acts as a progress indicator to the user.

It then adds logic that waits for the page to load and click events to the navigation buttons to enable cycling through the different form sections. If you refresh your page, you will see that the multi-step form works as expected.

Multi-step form navigation

Let’s dive deeper into what the Javascript code above is doing. In the updateStepVisibility() function, we first hide all the sections to have a clean slate:

formSteps.forEach((step) => {
  document.getElementById(step).style.display = "none";
});

Then, we show the currently active section:

document.getElementById(formSteps[currentStep]).style.display = "block";`

Next, we update the text that indicators progress through the form:

stepInfo.textContent = `Step ${currentStep + 1} of ${formSteps.length}`;

Finally, we hide the Previous button if we are at the first step and hide the Next button if we are at the last section:

navLeft.style.display = currentStep === 0 ? "none" : "block";
navRight.style.display = currentStep === formSteps.length - 1 ? "none" : "block";

Let’s look at what happens when the page loads. We first hide the Previous button as the form loads on the first section:

document.addEventListener("DOMContentLoaded", () => {
navLeft.style.display = "none";
updateStepVisibility();

Then we grab the Next button and add a click event that conditionally increments the current step count and then calls the updateStepVisibility() function, which then updates the new section to be displayed:

navRight.addEventListener("click", () => {
  if (currentStep < formSteps.length - 1) {
    currentStep++;
    updateStepVisibility();
  }
});

Finally, we grab the Previous button and do the same thing but in reverse. Here, we are conditionally decrementing the step count and calling the updateStepVisibility():

navLeft.addEventListener("click", () => {
  if (currentStep > 0) {
    currentStep--;
    updateStepVisibility();
  }
});

Handling errors

Have you ever spent a good 10+ minutes filling out a form only to submit it and get vague errors telling you to correct this and that? I prefer it when a form tells me right away that something’s amiss so that I can correct it before I ever get to the Submit button. That’s what we’ll do in our form.

Our principle is to clearly indicate which controls have errors and give meaningful error messages. Clear errors as the user takes necessary actions. Let’s add some validation to our form. First, let’s grab the necessary input elements and add this to the existing ones:

const nameInput = document.getElementById("name");
const idNumInput = document.getElementById("idNum");
const emailInput = document.getElementById("email");
const birthdateInput = document.getElementById("birthdate")
const documentInput = document.getElementById("document");
const departmentInput = document.getElementById("department");
const termsCheckbox = document.getElementById("terms");
const skillsInput = document.getElementById("skills");

Then, add a function to validate the steps:

Open validation script
function validateStep(step) {
  let isValid = true;
  
  if (step === 0) {
    if (nameInput.value.trim() === "") 
    showError(nameInput, "Name is required");
    isValid = false;
  }

  if (idNumInput.value.trim() === "") {
    showError(idNumInput, "ID number is required");
    isValid = false;
  }

  if (emailInput.value.trim() === "" || !emailInput.validity.valid) {
    showError(emailInput, "A valid email is required");
    isValid = false;
  }

  if (birthdateInput.value === "") {
    showError(birthdateInput, "Date of birth is required");
    isValid = false;
  }
  
  else if (step === 1) {

    if (!documentInput.files[0]) {
      showError(documentInput, "CV is required");
      isValid = false;
    }

    if (departmentInput.value === "") {
      showError(departmentInput, "Department selection is required");
      isValid = false;
    }
  } else if (step === 2) {

    if (!termsCheckbox.checked) {
      showError(termsCheckbox, "You must accept the terms and conditions");
      isValid = false;
    }
  }

  return isValid;
}

Here, we check if each required input has some value and if the email input has a valid input. Then, we set the isValid boolean accordingly. We also call a showError() function, which we haven’t defined yet.

Paste this code above the validateStep() function:

function showError(input, message) {
  const formControl = input.parentElement;
  const errorSpan = formControl.querySelector(".error-message");
  input.classList.add("error");
  errorSpan.textContent = message;
}

Now, add the following styles to the stylesheet:

Open validation styles
input:focus, select:focus, textarea:focus {
  outline: .5px solid var(--primary-color);
}

input.error, select.error, textarea.error {
  outline: .5px solid red;
}

.error-message {
  font-size: x-small;
  color: red;
  display: block;
  margin-top: 2px;
}

.arrows {
  color: var(--primary-color);
  font-size: 18px;
  font-weight: 900;

}

#navLeft, #navRight {
  display: flex;
  align-items: center;
  gap: 10px;
}

#stepInfo {
  color: var(--primary-color);
}

If you refresh the form, you will see that the buttons do not take you to the next section till the inputs are considered valid:

A personnel registration form with fields for name, ID number, email, and date of birth, all marked as required. Step 1 of 3 with a "Next" button.

Finally, we want to add real-time error handling so that the errors go away when the user starts inputting the correct information. Add this function below the validateStep() function:

Open real-time validation script
function setupRealtimeValidation() {
  nameInput.addEventListener("input", () => {
    if (nameInput.value.trim() !== "") clearError(nameInput);
  });

  idNumInput.addEventListener("input", () => {
    if (idNumInput.value.trim() !== "") clearError(idNumInput);
  });
  
  emailInput.addEventListener("input", () => {
    if (emailInput.validity.valid) clearError(emailInput);
  });
  
  birthdateInput.addEventListener("change", () => {
    if (birthdateInput.value !== "") clearError(birthdateInput);
  });
  
  documentInput.addEventListener("change", () => {
    if (documentInput.files[0]) clearError(documentInput);
  });
  
  departmentInput.addEventListener("change", () => {
    if (departmentInput.value !== "") clearError(departmentInput);
  });
  
  termsCheckbox.addEventListener("change", () => {
    if (termsCheckbox.checked) clearError(termsCheckbox);
  });
}

This function clears the errors if the input is no longer invalid by listening to input and change events then calling a function to clear the errors. Paste the clearError() function below the showError() one:

function clearError(input) {
  const formControl = input.parentElement;
  const errorSpan = formControl.querySelector(".error-message");
  input.classList.remove("error");
  errorSpan.textContent = "";
}

And now the errors clear when the user types in the correct value:

A personnel registration form with fields for name, email, ID number, and date of birth. Error messages indicate missing or invalid input. A "Step 1 of 3" label and "Next" button are shown.

The multi-step form now handles errors gracefully. If you do decide to keep the errors till the end of the form, then at the very least, jump the user back to the erroring form control and show some indication of how many errors they need to fix.

Handling form submission

In a multi-step form, it is valuable to show the user a summary of all their answers at the end before they submit and to offer them an option to edit their answers if necessary. The person can’t see the previous steps without navigating backward, so showing a summary at the last step gives assurance and a chance to correct any mistakes.

Let’s add a fourth section to the markup to hold this summary view and move the submit button within it. Paste this just below the third section in index.html:

Open HTML
<section class="group-four" id="four">
  <div class="summary-section">
    <p>Name: </p>
    <p id="name-val"></p>
    <button type="button" class="edit-btn" id="name-edit">
    <span>✎</span>
    <span>Edit</span>
    </button>
  </div>
  
  <div class="summary-section">
    <p>ID Number: </p>
    <p id="id-val"></p>
    <button type="button" class="edit-btn" id="id-edit">
    <span>✎</span>
    <span>Edit</span>
    </button>
  </div>
  
  <div class="summary-section">
    <p>Email: </p>
    <p id="email-val"></p>
    <button type="button" class="edit-btn" id="email-edit">
    <span>✎</span>
    <span>Edit</span>
    </button>
  </div>
  
  <div class="summary-section">
    <p>Date of Birth: </p>
    <p id="bd-val"></p>
    <button type="button" class="edit-btn" id="bd-edit">
    <span>✎</span>
    <span>Edit</span>
    </button>
  </div>
  
  <div class="summary-section">
    <p>CV/Resume: </p>
    <p id="cv-val"></p>
    <button type="button" class="edit-btn" id="cv-edit">
      <span>✎</span>
      <span>Edit</span>
    </button>
  </div>
    
  <div class="summary-section">
    <p>Department: </p>
    <p id="dept-val"></p>
    <button type="button" class="edit-btn" id="dept-edit">
      <span>✎</span>
      <span>Edit</span>
    </button>
  </div>
    
  <div class="summary-section">
    <p>Skills: </p>
    <p id="skills-val"></p>
    <button type="button" class="edit-btn" id="skills-edit">
      <span>✎</span>
      <span>Edit</span>
    </button>
  </div>
  
  <button id="btn" type="submit">Confirm and Submit</button>
</section>

Then update the formStep in your Javascript to read:

const formSteps = ["one", "two", "three", "four"];

Finally, add the following classes to styles.css:

.summary-section {
  display: flex;
  align-items: center;
  gap: 10px;
}

.summary-section p:first-child {
  width: 30%;
  flex-shrink: 0;
  border-right: 1px solid var(--secondary-color);
}

.summary-section p:nth-child(2) {
  width: 45%;
  flex-shrink: 0;
  padding-left: 10px;
}

.edit-btn {
  width: 25%;
  margin-left: auto;
  background-color: transparent;
  color: var(--primary-color);
  border: .7px solid var(--primary-color);
  border-radius: 5px;
  padding: 5px;
}

.edit-btn:hover {
  border: 2px solid var(--primary-color);
  font-weight: bolder;
  background-color: transparent;
}

Now, add the following to the top of the script.js file where the other consts are:

const nameVal = document.getElementById("name-val");
const idVal = document.getElementById("id-val");
const emailVal = document.getElementById("email-val");
const bdVal = document.getElementById("bd-val")
const cvVal = document.getElementById("cv-val");
const deptVal = document.getElementById("dept-val");
const skillsVal = document.getElementById("skills-val");
const editButtons = 
  "name-edit": 0,
  "id-edit": 0,
  "email-edit": 0,
  "bd-edit": 0,
  "cv-edit": 1,
  "dept-edit": 1,
  "skills-edit": 2
};

Then add this function in scripts.js:

function updateSummaryValues() {
  nameVal.textContent = nameInput.value;
  idVal.textContent = idNumInput.value;
  emailVal.textContent = emailInput.value;
  bdVal.textContent = birthdateInput.value;

  const fileName = documentInput.files[0]?.name;
  if (fileName) 
  const extension = fileName.split(".").pop();
  const baseName = fileName.split(".")[0];
  const truncatedName = baseName.length > 10 ? baseName.substring(0, 10) + "..." : baseName;
  cvVal.textContent = `${truncatedName}.${extension}`;
  } else {
    cvVal.textContent = "No file selected";
  }

  deptVal.textContent = departmentInput.value;
  skillsVal.textContent = skillsInput.value || "No skills submitted";
}

This dynamically inserts the input values into the summary section of the form, truncates the file names, and offers a fallback text for the input that was not required.

Then update the updateStepVisibility() function to call the new function:

function updateStepVisibility() {
  formSteps.forEach((step) => {
    document.getElementById(step).style.display = "none";
  });

  document.getElementById(formSteps[currentStep]).style.display = "block";
  stepInfo.textContent = `Step ${currentStep + 1} of ${formSteps.length}`;
  if (currentStep === 3) {
    updateSummaryValues();
  }

  navLeft.style.display = currentStep === 0 ? "none" : "block";
  navRight.style.display = currentStep === formSteps.length - 1 ? "none" : "block";
}

Finally, add this to the DOMContentLoaded event listener:

Object.keys(editButtons).forEach((buttonId) => {
  const button = document.getElementById(buttonId);
  button.addEventListener("click", (e) => {
    currentStep = editButtons[buttonId];
    updateStepVisibility();
  });
});

Running the form, you should see that the summary section shows all the inputted values and allows the user to edit any before submitting the information:

Personnel registration form displaying personal details with options to edit each field, a "Confirm and Submit" button, and navigation for previous steps.

And now, we can submit our form:

form.addEventListener("submit", (e) => {
  e.preventDefault();

  if (validateStep(2)) {
    alert("Form submitted successfully!");
    form.reset();
    currentFormStep = 0;
    updateStepVisibility();
}
});

Our multi-step form now allows the user to edit and see all the information they provide before submitting it.

Accessibility tips

Making multi-step forms accessible starts with the basics: using semantic HTML. This is half the battle. It is closely followed by using appropriate form labels.

Other ways to make forms more accessible include giving enough room to elements that must be clicked on small screens and giving meaningful descriptions to the form navigation and progress indicators.

Offering feedback to the user is an important part of it; it’s not great to auto-dismiss user feedback after a certain amount of time but to allow the user to dismiss it themselves. Paying attention to contrast and font choice is important, too, as they both affect how readable your form is.

Let’s make the following adjustments to the markup for more technical accessibility:

  1. Add aria-required="true" to all inputs except the skills one. This lets screen readers know the fields are required without relying on native validation.
  2. Add role="alert" to the error spans. This helps screen readers know to give it importance when the input is in an error state.
  3. Add role="status" aria-live="polite" to the .stepInfo. This will help screen readers understand that the step info keeps tabs on a state, and the aria-live being set to polite indicates that should the value change, it does not need to immediately announce it.

In the script file, replace the showError() and clearError() functions with the following:

function showError(input, message) {
  const formControl = input.parentElement;
  const errorSpan = formControl.querySelector(".error-message");
  input.classList.add("error");
  input.setAttribute("aria-invalid", "true");
  input.setAttribute("aria-describedby", errorSpan.id);
  errorSpan.textContent = message;
  }

  function clearError(input) {
  const formControl = input.parentElement;
  const errorSpan = formControl.querySelector(".error-message");
  input.classList.remove("error");
  input.removeAttribute("aria-invalid");
  input.removeAttribute("aria-describedby");
  errorSpan.textContent = "";
}

Here, we programmatically add and remove attributes that explicitly tie the input with its error span and show that it is in an invalid state.

Finally, let’s add focus on the first input of every section; add the following code to the end of the updateStepVisibility() function:

const currentStepElement = document.getElementById(formSteps[currentStep]);
const firstInput = currentStepElement.querySelector(
  "input, select, textarea"
);

if (firstInput) {
  firstInput.focus();
}

And with that, the multi-step form is much more accessible.

Conclusion

There we go, a four-part multi-step form for a job application! As I said at the top of this article, there’s a lot to juggle — so much so that I wouldn’t fault you for looking for an out-of-the-box solution.

But if you have to hand-roll a multi-step form, hopefully now you see it’s not a death sentence. There’s a happy path that gets you there, complete with navigation and validation, without turning away from good, accessible practices.

And this is just how I approached it! Again, I took this on as a personal challenge to see how far I could get, and I’m pretty happy with it. But I’d love to know if you see additional opportunities to make this even more mindful of the user experience and considerate of accessibility.

References

Here are some relevant links I referred to when writing this article:

  1. How to Structure a Web Form (MDN)
  2. Multi-page Forms (W3C.org)
  3. Create accessible forms (A11y Project)

How to Create Multi-Step Forms With Vanilla JavaScript and CSS originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.



from CSS-Tricks https://ift.tt/HI5rR30
via IFTTT

Tuesday, December 17, 2024

Mostly Clear/Wind today!



With a high of F and a low of 21F. Currently, it's 32F and Mostly Cloudy outside.

Current wind speeds: 14 from the North

Pollen: 0

Sunrise: December 17, 2024 at 08:05PM

Sunset: December 18, 2024 at 05:30AM

UV index: 0

Humidity: 64%

via https://ift.tt/EWAsfeX

December 18, 2024 at 10:02AM

What ELSE is on your CSS wishlist?

What else do we want or need CSS to do? It’s like being out late at night someplace you shouldn’t be and a stranger in a trenchcoat walks up and whispers in your ear.

“Psst. You wanna buy some async @imports? I’ve got the specificity you want.”

You know you shouldn’t entertain the idea but you do it anyway. All your friends doing Cascade Layers. What are you, a square?

I keep thinking of how amazing it is to write CSS today. There was an email exchange just this morning where I was discussing a bunch of ideas for a persistent set of controls in the UI that would have sounded bonkers even one year ago if it wasn’t for new features, like anchor positioning, scroll timelines, auto-height transitions, and popovers. We’re still in the early days of all these things — among many, many more — and have yet to see all the awesome possibilities come to fruition. Exciting times!

Chris kept a CSS wishlist, going back as far as 2013 and following up on it in 2019. We all have things we’d like to see CSS do and we always will no matter how many sparkly new features we get. Let’s revisit the ones from 2013:

  1. “I’d like to be able to select an element based on if it contains another particular selector.” Hello, :has()!
  2. ❌ “I’d like to be able to select an element based on the content it contains.”
  3. ❌ “I’d like multiple pseudo-elements.”
  4. ✅ “I’d like to be able to animate/transition something to height: auto;” Yep, we got that!
  5. 🟠 “I’d like things from Sass, like @extend@mixin, and nesting.” We got the nesting part down with some progress on mixins.
  6. ❌ “I’d like ::nth-letter::nth-word, etc.”
  7. ✅ “I’d like all the major browsers to auto-update.” This one was already fulfilled.

So, about a score of 3.5 out of 7. It could very well be that some of these things fell out of favor at some point (haven’t heard any crying for a new pseudo-element since the first wishlist). Chris re-articulated the list this way:

  • Parent queries. As in, selecting an element any-which-way, then selecting the parent of that element. We have some proof it’s possible with :focus-within.
  • Container queries. Select a particular element when the element itself is under certain conditions.
  • Standardized styling of form elements.
  • Has/Contains Selectors.
  • Transitions to auto dimensions.
  • Fixed up handling of viewport units.

And we’ve got the vast majority of those under wraps! We have ways to query parents and containers. We’re exploring stylable selects and field-sizing. We know about :has() and we’re still going gaga over transitions to intrinsic sizes. We’ve openly opined whether there’s too much CSS (there isn’t).

But what else is on your CSS wishlist? Ironically enough, Adam Argyle went through this exercise just this morning and I love the way he’s broken things down into a user-facing wishlist and a developer-facing wishlist. I mean, geez, a CSS carousel? Yes, please! I love his list and all lists like it.

We’ll round things up and put a list together — so let us know!


What ELSE is on your CSS wishlist? originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.



from CSS-Tricks https://ift.tt/T0yjI3P
via IFTTT

Monday, December 16, 2024

Cloudy today!



With a high of F and a low of 27F. Currently, it's 30F and Fair outside.

Current wind speeds: 15 from the South

Pollen: 0

Sunrise: December 16, 2024 at 08:05PM

Sunset: December 17, 2024 at 05:30AM

UV index: 0

Humidity: 49%

via https://ift.tt/ZdWs34D

December 17, 2024 at 10:02AM

Sunday, December 15, 2024

Clear today!



With a high of F and a low of 21F. Currently, it's 33F and Clear outside.

Current wind speeds: 7 from the Northwest

Pollen: 0

Sunrise: December 15, 2024 at 08:04PM

Sunset: December 16, 2024 at 05:29AM

UV index: 0

Humidity: 51%

via https://ift.tt/etVb0yO

December 16, 2024 at 10:02AM

Saturday, December 14, 2024

Mostly Clear today!



With a high of F and a low of 31F. Currently, it's 35F and Clear outside.

Current wind speeds: 12 from the South

Pollen: 0

Sunrise: December 14, 2024 at 08:03PM

Sunset: December 15, 2024 at 05:29AM

UV index: 0

Humidity: 47%

via https://ift.tt/K15cjzr

December 15, 2024 at 10:02AM

Friday, December 13, 2024

Partly Cloudy today!



With a high of F and a low of 25F. Currently, it's 33F and Partly Cloudy outside.

Current wind speeds: 8 from the Northwest

Pollen: 0

Sunrise: December 13, 2024 at 08:03PM

Sunset: December 14, 2024 at 05:29AM

UV index: 0

Humidity: 64%

via https://ift.tt/8O2iPI9

December 14, 2024 at 10:02AM

Thursday, December 12, 2024

Mostly Cloudy today!



With a high of F and a low of 19F. Currently, it's 28F and Partly Cloudy outside.

Current wind speeds: 11 from the Southeast

Pollen: 0

Sunrise: December 12, 2024 at 08:02PM

Sunset: December 13, 2024 at 05:29AM

UV index: 0

Humidity: 69%

via https://ift.tt/nweso5r

December 13, 2024 at 10:02AM

Wednesday, December 11, 2024

Partly Cloudy today!



With a high of F and a low of 24F. Currently, it's 30F and Clear outside.

Current wind speeds: 8 from the Southwest

Pollen: 0

Sunrise: December 11, 2024 at 08:01PM

Sunset: December 12, 2024 at 05:28AM

UV index: 0

Humidity: 59%

via https://ift.tt/z24pJ8Z

December 12, 2024 at 10:02AM

Fluid Superscripts and Subscripts

Superscripts and subscripts are essential elements in academic and scientific content — from citation references to chemical formulas and mathematical expressions. Yet browsers handle these elements with a static approach that can create significant problems: elements become either too small on mobile devices or disproportionately large on desktop displays.

After years of wrestling with superscript and subscript scaling in CSS, I’m proposing a modern solution using fluid calculations. In this article, I’ll show you why the static approach falls short and how we can provide better typography across all viewports while maintaining accessibility. Best of all, this solution requires nothing but clean, pure CSS.

The problem with static scaling

The scaling issue is particularly evident when comparing professional typography with browser defaults. Take this example (adapted from Wikipedia), where the first “2” is professionally designed and included in the glyph set, while the second uses <sub> (top) and <sup> (bottom) elements:

Diagramming the typographic parts and spacing of subscripts and superscripts.

Browsers have historically used font-size: smaller for <sup> and <sub> elements, which translates to roughly 0.83x scaling. While this made sense in the early days of CSS for simple documents, it can create problems in modern responsive designs where font sizes can vary dramatically. This is especially true when using fluid typography, where text sizes can scale smoothly between extremes.

Fluid scaling: A better solution

I’ve developed a solution that scales more naturally across different sizes by combining fixed and proportional units. This approach ensures legibility at small sizes while maintaining proper proportions at larger sizes, eliminating the need for context-specific adjustments.

Here’s how it works:

sup, sub {
  font-size: calc(0.5em + 4px);
  vertical-align: baseline;
  position: relative; 
  top: calc(-0.5 * 0.83 * 2 * (1em - 4px)); 
  /* Simplified top: calc(-0.83em + 3.32px) */
}

sub {
  top: calc(0.25 * 0.83 * 2 * (1em - 4px)); 
  /* Simplified top: calc(0.42em - 1.66px) */
}
  • Natural scaling: The degressive formula ensures that superscripts and subscripts remain proportional at all sizes
  • Baseline alignment: By using vertical-align: baseline and relative positioning, we prevent the elements from affecting line height and it gives us better control over the offset to match your specific needs. You’re probably also wondering where the heck these values come from — I’ll explain in the following.

Breaking down the math

Let’s look at how this works, piece by piece:

Calculating the font size (px)

At small sizes, the fixed 4px component has more impact. At large sizes, the 0.5em proportion becomes dominant. The result is more natural scaling across all sizes.

sup, sub {
  font-size: calc(0.5em + 4px);
  /* ... */
}

sub { 
  /* ... */
}

Calculating the parent font size (em)

Within the <sup> and <sub> elements, we can calculate the parent’s font-size:

sup, sub {
  font-size: calc(0.5em + 4px);
  top: calc(2 * (1em - 4px));
}

sub { 
  top: calc(2 * (1em + 4px));
}

The fluid font size is defined as calc(0.5em + 4px). To compensate for the 0.5em, we first need to solve 0.5em * x = 1em which gives us x = 2. The 1em here represents the font size of the <sup> and <sub> elements themselves. We subtract the 4px fixed component from our current em value before multiplying.

The vertical offset

For the vertical offset, we start with default CSS positioning values and adjust them to work with our fluid scaling:

sup, sub {
  font-size: calc(0.5em + 4px);
  top: calc(-0.5 * 0.83 * 2 * (1em - 4px));
}

sub { 
  top: calc(0.25 * 0.83 * 2 * (1em - 4px));
}

The formula is carefully calibrated to match standard browser positioning:

  • 0.5em (super) and 0.25em (sub) are the default vertical offset values (e.g. used in frameworks like Tailwind CSS and Bootstrap).
  • We multiply by 0.83 to account for the browser’s font-size: smaller scaling factor, which is used per default for superscript and subscript.

This approach ensures that our superscripts and subscripts maintain familiar vertical positions while benefiting from improved fluid scaling. The result matches what users expect from traditional browser rendering but scales more naturally across different font sizes.

Helpful tips

The exact scaling factor font-size: (0.5em + 4px) is based on my analysis of superscript Unicode characters in common fonts. Feel free to adjust these values to match your specific design needs. Here are a few ways how you might want to customize this approach:

For larger scaling:

sup, sub {
  font-size: calc(0.6em + 3px);
  /* adjust offset calculations accordingly */
}

For smaller scaling:

sup, sub {
  font-size: calc(0.4em + 5px);
  /* adjust offset calculations accordingly */
}

For backward compatibility, you might want to wrap all of it in a @supports block:

@supports (font-size: calc(1em + 1px)) {
  sup, sub {
    ...
  }
}

Final demo

I built this small interactive demo to show different fluid scaling options, compare them to the browser’s static scaling, and fine-tune the vertical positioning to see what works best for your use case:

Give it a try in your next project and happy to hear your thoughts!


Fluid Superscripts and Subscripts originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.



from CSS-Tricks https://ift.tt/rDGIgpR
via IFTTT

Tuesday, December 10, 2024

Partly Cloudy today!



With a high of F and a low of 20F. Currently, it's 27F and Mostly Cloudy outside.

Current wind speeds: 9 from the West

Pollen: 0

Sunrise: December 10, 2024 at 08:00PM

Sunset: December 11, 2024 at 05:28AM

UV index: 0

Humidity: 55%

via https://ift.tt/pbKJnou

December 11, 2024 at 10:02AM

CSSWG Minutes Telecon (2024-12-04): Just Use Grid vs. Display: Masonry

The CSS Working Group (CSSWG) meets weekly (or close to it) to discuss and quickly resolve issues from their GitHub that would otherwise be lost in the back-and-forth of forum conversation. While each meeting brings interesting conversation, this past Wednesday (December 4th) was special. The CSSWG met to try and finally squash a debate that has been going on for five years: whether Masonry should be a part of Grid or a separate system.

I’ll try to summarize the current state of the debate, but if you are looking for the long version, I recommend reading CSS Masonry & CSS Grid by Geoff and Choosing a Masonry Syntax in CSS by Miriam Suzanne.

In 2017, it was frequently asked whether Grid could handle masonry layouts; layouts where the columns (or the rows) could hold unevenly sized items without gaps in between. While this is just one of several possibilities with masonry, you can think about the layout popularized by Pinterest:

Pinterest showing a masonry layout

In 2020, Firefox released a prototype in which masonry was integrated into the CSS Grid layout module. The main voice against it was Rachel Andrew, arguing that it should be its own, separate thing. Since then, the debate has escalated with two proposals from Apple and Google, arguing for and against a grid-integrated syntax, respectively.

There were some technical worries against a grid-masonry implementation that were since resolved. What you have to know is this: right now, it’s a matter of syntax. To be specific, which syntax is

a. is easier to learn for authors and

b. how might this decision impact possible future developments in one or both models (or CSS in general).

In the middle, the W3C Technical Architecture Group (TAG) was asked for input on the issue which has prompted an effort to unify the two proposals. Both sides have brought strong arguments to the table over a series of posts, and in the following meeting, they were asked to lay those arguments once again in a presentation, with the hope of reaching a consensus.

Remember that you can subscribe and read the full minutes on W3C.org

The Battle of PowerPoints

Alison Maher representing Google and an advocate of implementing Masonry as a new display value, opened the meeting with a presentation. The main points were:

  1. Several properties behave differently between masonry and grid.
  2. Better defaults when setting display: masonry, something that Rachel Andrew recently argued for.
  3. There was an argument against display: masonry since fallbacks would be more lengthy to implement, whereas in a grid-integrated the fallback to grid is already there. Alison Maher refutes this since “needing one is a temporary problem, so [we] should focus on the future,” and that “authors should make explicit fallback, to avoid surprises.”
  4. “Positioning in masonry is simpler than grid, it’s only placed in 1 axis instead of 2.”
  5. Shorthands are also better: “Grid shorthand is complicated, hard to use. Masonry shorthand is easier because don’t need to remember the order.”
  6. “Placement works differently in grid vs masonry” and “alignment is also very different”
  7. There will be “other changes for submasonry/subgrid that will lead to divergences.”
  8. “Integrating masonry into grid will lead to spec bloat, will be harder to teach, and lead to developer confusion.”

alisonmaher: “Conclusion: masonry should be a separate display type”

Jen Simmons, representing the WebKit team and advocate of the “Just Use Grid” approach followed with another presentation. On this side, the main points were:

  1. Author learning could be skewed since “a new layout type creates a separate tool with separate syntax that’s similar but not the same as what exists […]. They’re familiar but not quite the same”
  2. The Chrome proposal would add around 10 new properties. “We don’t believe there’s a compelling argument to add so many new properties to CSS.”
Table of properties that display: masonry would add. (around 10 new properties)
  1. “Chromium argues that their new syntax is more understandable. We disagree, just use grid-auto-flow
  2. “When you layout rows in grid, template syntax is a bit different — you stack the template names to physically diagram the names for the rows. Just Use Grid re-uses this syntax exactly; but new masonry layout uses the column syntax for rows”
  3. “Other difference is the auto-flow — grid’s indicates the primary fill direction, Chrome believes this doesn’t make sense and changed it to match the orientation of lines”
Difference in template syntax between Just use Grid vs display: masonry
  1. “Chrome argues that new display type allows better defaults — but the defaults propose aren’t good […] it doesn’t quite work as easily as claimed [see article] requires deep understanding of autosizing”
  2. “Easier to switch, e.g. at breakpoints or progressive enhancement”
  3. “Follows CSS design principles to re-use what already exists”

The TAG review

After two presentations with compelling arguments, Lea Verou (also a member of the TAG) followed with their input.

lea: We did a TAG review on this. My opinion is fully reflected there. I think the arguments WebKit team makes are compelling. We thought not only should masonry be part of grid, but should go further. A lot of arguments for integrating is that “grid is too hard”. In that case we should make grid things easier. Complex things are possible, but simple things are not so easy.

Big part of Google’s argument is defaults, but we could just have smarter defaults — there is precedent for this in CSS if we decided that would help ergonomics We agree that switching between grid vs. masonry is common. Grid might be a slightly better fallback than nothing, but minor argument because people can use @supports. Introducing all these new properties increasing the API surfaces that authors need to learn. Less they can port over. Even if we say we will be disciplined, experience shows that we won’t. Even if not intentional, accidental. DRY – don’t have multiple sources of truth

One of arguments against masonry in grid is that grids are 2D, but actually in graphic design grids were often 1D. I agree that most masonry use cases need simpler grids than general grid use cases, but that means we should make those grids easier to define for both grid and masonry. The more we looked into this, we realize there are 3 different layout modes that give you 2D arrangement of children. We recommended not just make masonry part of grid, but find ways of integrating what we already have better could we come up with a shorthand that sets grid-auto-flow and flex-direction, and promote that for layout direction in general? Then authors only need to learn one control for it.

The debate

All was laid out onto the table, it was only left what other members had to say.

oriol: Problem with Jen Simmons’s reasoning. She said the proposed masonry-direction property would be new syntax that doesn’t match grid-auto-flow property, but this property matches flex-direction property so instead of trying to be close to grid, tries to be close to flexbox. Closer to grid is a choice, could be consistent with different things.

astearns: One question I asked is, has anyone changed their mind on which proposal they support? I personally have. I thought that separate display property made a lot more sense, in terms of designing the feature and I was very daunted by the idea that we’d have to consider both grid and masonry for any new development in either seemed sticky to me but the TAG argument convinced me that we should do the work of integrating these things.

TabAtkins: Thanks for setting that up for me, because I’m going to refute the TAG argument! I think they’re wrong in this case. You can draw a lot of surface-level connections between Grid and Masonry, and Flexbox, and other hypothetical layouts but when you actually look at details of how they work, behaviors each one is capable of, they’re pretty distinct if you try to combine together, it would be an unholy mess of conflicting constraints — e.g. flexing in items of masonry or grid or you’d have a weird mish-mash of, “the 2D layout.

But if you call it a flex you get access to these properties, call it grid, access to these other properties concrete example, “pillar” example mentioned in webKit blog post, that wasn’t compatible with the base concepts in masonry and flex because it wants a shared block formatting context grid etc have different formatting contexts, can’t use floats.

lea: actually, the TAG argument was that layout seems to actually be a continuum, and syntax should accommodate that rather than forcing one of two extremes (current flex vs current grid).

The debate kept back and forth until there was an attempt to set a general north star to follow.

jyasskin: Wanted to emphasize a couple aspects of TAG review. It seems really nice to keep the property from Chrome proposal that you don’t have to learn both, can just learn to do masonry without learning all of Grid even if that’s in a unified system perhaps still define masonry shorthand, and have it set grid propertie

jensimmons: To create a simple masonry-style layout in Grid, you just need 3 lines of code (4 with a gap). It’s quite simple.

jyasskin: Most consensus part of TAG feedback was to share properties whenever possible. Not necessary to share the same ‘display’ values; could define different ‘display’ values but share the properties. One thing we didn’t like about unified proposal was grid-auto-flow in the unified proposal, where some values were ignored. Yeah, this is the usability point I’m pounding on

Another Split Decision

Despite all, it looked like nobody was giving away, and the debate seemed stuck once again:

astearns: I’m not hearing a way forward yet. At some point, one of the camps is going to have to concede in order to move this forward.

lea: What if we do a straw poll. Not to decide, but to figure out how far are we from consensus? +1 lea

The votes were cast and the results were… split.

Straw Poll for Just Use Grid, showing a split result. 12 NOs, 13 YES and 5 ABSTAIN

florian: though we could still not reach consensus, I want to thank both sides for presenting clear arguments, densely packed, well delivered. I will go back to the presentations, and revisit some points, it really was informative to present the way it was.

That’s all folks, a split decision! There isn’t a preference for either of the two proposals and implementing something with such mixed opinions is something nobody would approve. After a little over five years of debate, I think this meeting is yet another good sign that a new proposal addressing the concerns of both sides should be considered, but that’s just a personal opinion. To me, masonry (or whatever name it may be) is an important step in CSS layout that may shape future layouts, it shouldn’t be rushed so until then, I am more than happy to wait for a proposal that satisfies both sides.

Further Reading

Relevant Issues


CSSWG Minutes Telecon (2024-12-04): Just Use Grid vs. Display: Masonry originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.



from CSS-Tricks https://ift.tt/6piOmbC
via IFTTT

Mostly Clear today!

With a high of F and a low of 15F. Currently, it's 14F and Clear outside. Current wind speeds: 13 from the Southwest Pollen: 0 S...