Ads
Sunday, October 31, 2021
Cloudy today!
With a high of F and a low of 28F. Currently, it's 39F and Cloudy outside.
Current wind speeds: 13 from the Southeast
Pollen: 2
Sunrise: October 31, 2021 at 07:18PM
Sunset: November 1, 2021 at 05:51AM
UV index: 0
Humidity: 58%
via https://ift.tt/2livfew
November 1, 2021 at 10:06AM
CSS-ing Candy Ghost Buttons
Recently, while looking for some ideas on what to code as I have zero artistic sense so the only thing I can do is find pretty things that other people have come up with and remake them with clean and compact code… I came across these candy ghost buttons!
They seemed like the perfect choice for a cool little thing I could quickly code. Less than fifteen minutes later, this was my Chromium result:
I thought the technique was worth sharing, so in this article, we’ll be going through how I first did it and what other options we have.
The starting point
A button is created with… are you ready for this? A button
element! This button
element has a data-ico
attribute in which we drop an emoji. It also has a stop list custom property, --slist
, set in the style
attribute.
<button data-ico="👻" style="--slist: #ffda5f, #f9376b">boo!</button>
After writing the article, I learned that Safari has a host of problems with clipping to text
, namely, it doesn’t work on button
elements, or on elements with display: flex
(and perhaps grid
too?), not to mention the text of an element’s children. Sadly, this means all of the techniques presented here fail in Safari. The only workaround is to apply all the button
styles from here on a span
element nested inside the button
, covering its parent’s border-box
. And, in case this helps anyone else who, like me, is on Linux without physical access to an Apple device (unless you count the iPhone 5 someone on the fourth floor — who you don’t want to bother with stuff like this more than twice a month anyway — bought recently), I’ve also learned to use Epiphany in the future.
For the CSS part, we add the icon in an ::after
pseudo-element and use a grid
layout on the button
in order to have nice alignment for both the text and the icon. On the button
, we also set a border
, a padding
, a border-radius
, use the stop list, --slist
, for a diagonal gradient and prettify the font
.
button {
display: grid;
grid-auto-flow: column;
grid-gap: .5em;
border: solid .25em transparent;
padding: 1em 1.5em;
border-radius: 9em;
background:
linear-gradient(to right bottom, var(--slist))
border-box;
font: 700 1.5em/ 1.25 ubuntu, sans-serif;
text-transform: uppercase;
&::after { content: attr(data-ico) }
}
There’s one thing to clarify about the code above. On the highlighted line, we set both the background-origin
and the background-clip
to border-box
. background-origin
both puts the 0 0
point for background-position
in the top-left corner of the box it’s set to and gives us the box whose dimensions the background-size
is relative to.
That is, if background-origin
is set to padding-box
, the 0 0
point for background-position
is in the top left-corner of the padding-box
. If background-origin
is set to border-box
, the 0 0
point for background-position
is in the top-left corner of the border-box
. If background-origin
is set to padding-box
, a background-size
of 50% 25%
means 50%
of the padding-box
width and 25%
of the padding-box
height. If background-origin
is set to border-box
, the same background-size
of 50% 25%
means 50%
of the border-box
width and 25%
of the border-box
height.
The default value for background-origin
is padding-box
, meaning that a default-sized 100% 100%
gradient will cover the padding-box
and then repeat itself underneath the border
(where we cannot see it if the border
is fully opaque). However, in our case, the border
is fully transparent
and we want our gradient to stretch across the entire border-box
. This means we need to change the background-origin
value to border-box
.
The simple, but sadly non-standard Chromium solution
This involves using three mask
layers and compositing them. If you need a refresher on mask
compositing, you can check out this crash course.
Note that in the case of CSS mask
layers, only the alpha channel matters, as every pixel of the masked element gets the alpha of the corresponding mask
pixel, while the RGB channels don’t influence the result in any way, so they may be any valid value. Below, you can see the effect of a purple
to transparent
gradient overlay versus the effect of using the exact same gradient as a mask
.
We’re going to start with the bottom two layers. The first one is a fully opaque layer, fully covering the entire border-box
, meaning that it has an alpha of 1
absolutely everywhere. The other one is also fully opaque, but restricted (by using mask-clip
) to the padding-box
, which means that, while this layer has an alpha of 1 all across the padding-box
, it’s fully transparent
in the border
area, having an alpha of 0
there.
If you have a hard time picturing this, a good trick is to think of an element’s layout boxes as nested rectangles, the way they’re illustrated below.
In our case, the bottom layer is fully opaque (the alpha value is 1
) across the entire orange box (the border-box
). The second layer, that we place on top of the first one, is fully opaque (the alpha value is 1
) all across the red box (the padding-box
) and fully transparent
(with an alpha of 0
) in the area between the padding
limit and the border
limit.
One really cool thing about the limits of these boxes is that corner rounding is determined by the border-radius
(and, in the case of the padding-box
, by the border-width
as well). This is illustrated by the interactive demo below, where we can see how the corner rounding of the border-box
is given by the border-radius
value, while the corner rounding of the padding-box
is computed as the border-radius
minus the border-width
(limited at 0
in case the difference is a negative value).
Now let’s get back to our mask
layers, one being fully opaque all across the entire border-box
, while the one on top of it is fully opaque across the padding-box
area and fully transparent in the border
area (between the padding
limit and the border
limit). These two layers get composited using the exclude
operation (called xor
in the non-standard WebKit version).
The name of this operation is pretty suggestive in the situation where the alphas of the two layers are either 0
or 1
, as they are in our case — the alpha of the first layer is 1
everywhere, while the alpha of the second layer (that we place on top of the first) is 1
inside the padding-box
and 0
in the border
area between the padding
limit and the border
limit.
In this situation, it’s pretty intuitive that the rules of boolean logic apply — XOR-ing two identical values gives us 0
, while XOR-ing two different ones gives us 1
.
All across the padding-box
, both the first layer and the second one have an alpha of 1
, so compositing them using this operation gives us an alpha of 0
for the resulting layer in this area. However, in the border
area (outside the padding
limit, but inside the border
limit), the first layer has an alpha of 1
, while the second one has an alpha of 0
, so we get an alpha of 1
for the resulting layer in this area.
This is illustrated by the interactive demo below, where you can switch between viewing the two mask
layers separated in 3D and viewing them stacked and composited using this operation.
Putting things into code, we have:
button {
/* same base styles */
--full: linear-gradient(red 0 0);
-webkit-mask: var(--full) padding-box, var(--full);
-webkit-mask-composite: xor;
mask: var(--full) padding-box exclude, var(--full);
}
Before we move further, let’s discuss a few fine-tuning details about the CSS above.
First off, since the fully opaque layers may be anything (the alpha channel is fixed, always 1
and the RGB channels don’t mater), I usually make them red
— only three characters! In the same vein, using a conic gradient instead of a linear one would also save us one character, but I rarely ever do that since we still have mobile browsers that support masking, but not conic gradients. Using a linear one ensures we have support all across the board. Well, save for IE and pre-Chromium Edge but, then again, not much cool shiny stuff works in those anyway.
Second, we’re using gradients for both layers. We’re not using a plain background-color
for the bottom one because we cannot set a separate background-clip
for the background-color
itself. If we were to have the background-image
layer clipped to the padding-box
, then this background-clip
value would also apply to the background-color
underneath — it would be clipped to the padding-box
too and we’d have no way to make it cover the entire border-box
.
Third, we’re not explicitly setting a mask-clip
value for the bottom layer since the default for this property is precisely the value we want in this case, border-box
.
Fourth, we can include the standard mask-composite
(supported by Firefox) in the mask
shorthand, but not the non-standard one (supported by WebKit browsers).
And finally, we always set the standard version last so it overrides any non-standard version that may also be supported.
The result of our code so far (still cross-browser at this point) looks like below. We’ve also added a background-image
on the root so that it’s obvious we have real transparency across the padding-box
area.
This is not what we want. While we have a nice gradient border
(and by the way, this is my preferred method of getting a gradient border
since we have real transparency all across the padding-box
and not just a cover), we are now missing the text.
So the next step is to add back the text using yet another mask
layer on top of the previous ones, this time one that’s restricted to text
(while also making the actual text fully transparent
so that we can see the gradient background
through it) and XOR this third mask
layer with the result of XOR-ing the first two (result that can be seen in the screenshot above).
The interactive demo below allows viewing the three mask
layers both separated in 3D as well as stacked and composited.
Note that the text
value for mask-clip
is non-standard, so, sadly, this only works in Chrome. In Firefox, we just don’t get any masking on the button anymore and having made the text transparent
, we don’t even get graceful degradation.
button {
/* same base styles */
-webkit-text-fill-color: transparent;
--full: linear-gradient(red 0 0);
-webkit-mask: var(--full) text, var(--full) padding-box, var(--full);
-webkit-mask-composite: xor;
/* sadly, still same result as before :( */
mask: var(--full) padding-box exclude, var(--full);
}
If we don’t want to make our button
unusable this way, we should put the code that applies the mask
and makes the text transparent
in a @supports
block.
button {
/* same base styles */
@supports (-webkit-mask-clip: text) {
-webkit-text-fill-color: transparent;
--full: linear-gradient(red 0 0);
-webkit-mask: var(--full) text, var(--full) padding-box, var(--full);
-webkit-mask-composite: xor;
}
}
I really like this method, it’s the simplest we have at this point and I’d really wish text
was a standard value for mask-clip
and all browsers supported it properly.
However, we also have a few other methods of achieving the candy ghost button effect, and although they’re either more convoluted or more limited than the non-standard Chromium-only one we’ve just discussed, they’re also better supported. So let’s take a look at those.
The extra pseudo-element solution
This involves setting the same initial styles as before, but, instead of using a mask
, we clip the background
to the text
area.
button {
/* same base styles */
background:
linear-gradient(to right bottom, var(--slist))
border-box;
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent
}
Just like before, we need to also make the actual text transparent
, so we can see through it to the pastel gradient background
behind it that is now clipped to its shape.
Alright, we have the gradient text, but now we’re missing the gradient border
. So we’re going to add it using an absolutely positioned ::before
pseudo-element that covers the entire border-box
area of the button
and inherits the border
, border-radius
and background
from its parent (save for the background-clip
, which gets reset to border-box
).
$b: .25em;
button {
/* same as before */
position: relative;
border: solid $b transparent;
&::before {
position: absolute;
z-index: -1;
inset: -$b;
border: inherit;
border-radius: inherit;
background: inherit;
background-clip: border-box;
content: '';
}
}
inset: -$b
is a shorthand for:
top: -$b;
right: -$b;
bottom: -$b;
left: -$b
Note that we’re using the border-width
value ($b
) with a minus sign here. The 0
value would make the margin-box
of the pseudo (identical to the border-box
in this case since we have no margin
on the ::before
) only cover the padding-box
of its button
parent and we want it to cover the entire border-box
. Also, the positive direction is inwards, but we need to go outwards by a border-width
to get from the padding
limit to the border
limit, hence the minus sign — we’re going in the negative direction.
We’ve also set a negative z-index
on this absolutely positioned element since we don’t want it to be on top of the button
text and prevent us from selecting it. At this point, text selection is the only way we can distinguish the text from the background
, but we’ll soon fix that!
Note that since pseudo-element content isn’t selectable, the selection only includes the button’s actual text content and not the emoji in the ::after
pseudo-element as well.
The next step is to add a two layer mask
with an exclude
compositing operation between them in order to leave just the border
area of this pseudo-element visible.
button {
/* same as before */
&::before {
/* same as before */
--full: linear-gradient(red 0 0);
-webkit-mask: var(--full) padding-box, var(--full);
-webkit-mask-composite: xor;
mask: var(--full) padding-box exclude, var(--full);
}
}
This is pretty much what we did for the actual button
in one of the intermediate stages of the previous method.
I find this to be the best approach in most cases when we want something cross-browser and that doesn’t include IE or pre-Chromium Edge, none of which ever supported masking.
The border-image
solution
At this point, some of you may be screaming at the screen that there’s no need to use the ::before
pseudo-element when we could use a gradient border-image
to create this sort of a ghost button — it’s a tactic that has worked for over three quarters of a decade!
However, there’s a very big problem with using border-image
for pill-shaped buttons: this property doesn’t play nice with border-radius
, as it can be seen in the interactive demo below. As soon as we set a border-image
on an element with border-radius
, we lose the corner rounding of the border
, even through, funny enough, the background
will still respect this rounding.
Still, this may be a simple solution in the case where don’t need corner rounding or the desired corner rounding is at most the size of the border-width
.
In the no corner rounding case, save for dropping the now pointless border-radius
, we don’t need to change the initial styles much:
button {
/* same base styles */
--img: linear-gradient(to right bottom, var(--slist));
border: solid .25em;
border-image: var(--img) 1;
background: var(--img) border-box;
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
}
The result can be seen below, cross-browser (should be supported supported even in pre-Chromium Edge).
The trick with the desired corner rounding being smaller than the border-width
relies on the way border-radius
works. When we set this property, the radius we set represents the rounding for the corners of the border-box
. The rounding for the corners of the padding-box
(which is the inner rounding of the border
) is the border-radius
minus the border-width
if this difference is positive and 0
(no rounding) otherwise. This means we have no inner rounding for the border
if the border-radius
is smaller than or equal to the border-width
.
In this situation, we can use the inset()
function as a clip-path
value since it also offers the possibility of rounding the corners of the clipping rectangle. If you need a refresher on the basics of this function, you can check out the illustration below:
inset()
cuts out everything outside a clipping rectangle defined by the distances to the edges of the element’s border-box
, specified the same way we’d specify margin
, border
or padding
(with one, two, three or four values) and the corner rounding for this rectangle, specified the same way we’d specify border-radius
(any valid border-radius
value is also valid here).
In our case, the distances to the edges of the border-box
are all 0
(we don’t want to chop anything off any of the edges of the button
), but we have a rounding that has to be at most at big as the border-width
so that not having any inner border
rounding makes sense.
$b: .25em;
button {
/* same as before */
border: solid $b transparent;
clip-path: inset(0 round $b)
}
Note that the clip-path
is also going to cut out any outer shadows we may add on the button
element, whether they’re added via box-shadow
or filter: drop-shadow()
.
While this technique cannot achieve the pill shape look, it does have the advantage of having great support nowadays and it may be all we need in certain situations.
The three solutions discussed so far can be seen compiled in the demo below, which also comes with a YouTube link where you can see me code each of them from scratch if you prefer to learn by watching things being built on video rather than reading about them.
All these methods create real transparency in the padding-box
outside the text, so they work for any background
we may have behind the button
. However, we also have a couple of other methods that may be worth mentioning, even though they come with restrictions in this department.
The cover solution
Just like the border-image
approach, this is a pretty limited tactic. It doesn’t work unless we have a solid or a fixed background
behind the button
.
It involves layering backgrounds with different background-clip
values, just like the cover technique for gradient borders. The only difference is that here we add one more gradient layer on top of the one emulating the background
behind our button
element and we clip this top layer to text
.
$c: #393939;
html { background: $c; }
button {
/* same as before */
--grad: linear-gradient(to right bottom, var(--slist));
border: solid .25em transparent;
border-radius: 9em;
background: var(--grad) border-box,
linear-gradient($c 0 0) /* emulate bg behind button */,
var(--grad) border-box;
-webkit-background-clip: text, padding-box, border-box;
-webkit-text-fill-color: transparent;
}
Sadly, this approach fails in Firefox due to an old bug — just not applying any background-clip
while also making the text transparent
produces a pill-shaped button with no visible text.
We could still make it cross-browser by using the cover method for the gradient border
on a ::before
pseudo and background-clip: text
on the actual button
, which is basically just a more limited version of the second solution we discussed — we still need to use a pseudo, but, since we use a cover, not a mask
, it only works if we have a solid or fixed background
behind the button
.
$b: .25em;
$c: #393939;
html { background: $c; }
button {
/* same base styles */
--grad: linear-gradient(to right bottom, var(--slist));
border: solid $b transparent;
background: var(--grad) border-box;
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
&::before {
position: absolute;
z-index: -1;
inset: -$b;
border: inherit;
border-radius: inherit;
background: linear-gradient($c 0 0) padding-box,
var(--grad) border-box;
content: '';
}
}
On the bright side, this more limited version should also work in pre-Chromium Edge.
Below, there’s also the fixed background
version.
$f: url(balls.jpg) 50%/ cover fixed;
html { background: $f; }
button {
/* same as before */
&::before {
/* same as before */
background: $f padding-box,
var(--grad) border-box
}
}
Overall, I don’t think this is the best tactic unless we both fit into the background
limitation and we need to reproduce the effect in browsers that don’t support masking, but support clipping the background
to the text
, such as pre-Chromium Edge.
The blending solution
This approach is another limited one as it won’t work unless, for each and every gradient pixel that’s visible, its channels have values that are either all bigger or all smaller than than the corresponding pixel of the background
underneath the button
. However, this is not the worst limitation to have as it should probably lead to our page having better contrast.
Here, we start by making the parts where we want to have the gradient (i.e. the text, icon and border
) either white
or black
, depending on whether we have a dark theme with a light gradient or a light theme with a dark gradient, respectively. The rest of the button
(the area around the text and icon, but inside the border
) is the inverse of the previously chosen color
(white
if we set the color
value to black
and black
otherwise).
In our case, we have a pretty light gradient button
on a dark background
, so we start with white
for the text, icon and border, and black
for the background
. The hex channel values of our two gradient stops are ff
(R
), da
(G
), 5f
(B
) and f9
(R
), 37
(G
), 6b
(B
), so we’d be safe with any background
pixels whose channel values are at most as big as min(ff, f9) = f9
for red, min(da, 37) = 37
for green and min(5f, 6b) = 5f
for blue.
This means having a background-color
behind our button
with channel values that are smaller or equal to f9
, 37
and 5f
, either on its own as a solid background
, or underneath a background-image
layer we blend with using the multiply
blend mode (which always produces a result that’s at least as dark as the darker of the two layers). We’re setting this background
on a pseudo-element since blending with the actual body
or the html
doesn’t work in Chrome.
$b: .25em;
body::before {
position: fixed;
inset: 0;
background: url(fog.jpg) 50%/ cover #f9375f;
background-blend-mode: multiply;
content: '';
}
button {
/* same base styles */
position: relative; /* so it shows on top of body::before */
border: solid $b;
background: #000;
color: #fff;
&::after {
filter: brightness(0) invert(1);
content: attr(data-ico);
}
}
Note that making the icon fully white
means making it first black
with brightness(0)
and then inverting this black
with invert(1)
.
We then add a gradient ::before
pseudo-element, just like we did for the first cross-browser method.
button {
/* same styles as before */
position: relative;
&::before {
position: absolute;
z-index: 2;
inset: -$b;
border-radius: inherit;
background: linear-gradient(to right bottom, var(--slist);
pointer-events: none;
content: '';
}
}
The only difference is that here, instead of giving it a negative z-index
, we give it a positive z-index
. That way it’s not just over the actual button
, but also over the ::after
pseudo and we set pointer-events
to none
in order to allow the mouse to interact with the actual button
content underneath.
Now the next step is to keep the black
parts of our button
, but replace the white
parts (i.e., the text, icon and border
) with the gradient. We can do this with a darken
blend mode, where the two layers are the black and white button with the ::after
icon and the gradient pseudo on top of it.
For each of the RGB channels, this blend mode takes the values of the two layers and uses the darker (smaller) one for the result. Since everything is darker than white
, the resulting layer uses the gradient pixel values in that area. Since black
is darker than everything, the resulting layer is black
everywhere the button
is black
.
button {
/* same styles as before */
&::before {
/* same styles as before */
mix-blend-mode: darken;
}
}
Alright, but we’d only be done at this point if the background
behind the button
was pure black
. Otherwise, in the case of a background
whose every pixel is darker than the corresponding pixel of the gradient on our button
, we can apply a second blend mode, this time lighten
on the actual button
(previously, we had darken
on the ::before
pseudo).
For each of the RGB channels, this blend mode takes the values of the two layers and uses the lighter (bigger) one for the result. Since anything is lighter than black
, the resulting layer uses the background
behind the button
everywhere the button
is black
. And since a requirement is that every gradient pixel of the button
is lighter than the corresponding pixel of the background
behind it, the resulting layer uses the gradient pixel values in that area.
button {
/* same styles as before */
mix-blend-mode: lighten;
}
For a dark gradient button
on a light background
, we need to switch up the blend modes. That is, use lighten
on the ::before
pseudo and darken
on the button
itself. And first of all, we need to ensure the background
behind the button
is light enough.
Let’s say our gradient is between #602749
and #b14623
. The channel values of our gradient stops are 60
(R
), 27
(G
), 49
(B
) and b1
(R
), 46
(G
), 23
(R
), so the background
behind the button
needs to have channel values that are at least max(60, b1) = b1
for red, max(27, 46) = 46
for green and max(49, 23) = 49
for blue.
This means having a background-color
on our button
with channel values that are bigger or equal to b1
, 46
and 49
, either on its own as a solid background
, or underneath a background-image
layer, uses a screen
blend mode (which always produces a result that’s at least as light as the lighter of the two layers).
We also need to make the button
border, text and icon black
, while setting its background
to white
:
$b: .25em;
section {
background: url(fog.jpg) 50%/ cover #b14649;
background-blend-mode: screen;
}
button {
/* same as before */
border: solid $b;
background: #fff;
color: #000;
mix-blend-mode: darken;
&::before {
/* same as before */
mix-blend-mode: lighten
}
&::after {
filter: brightness(0);
content: attr(data-ico);
}
}
The icon in the ::after
pseudo-element is made black
by setting filter: brightness(0)
on it.
We also have the option of blending all the button
layers as a part of its background
, both for the light and dark theme, but, as mentioned before, Firefox just ignores any background-clip
declaration where text
is a part of a list of values and not the single value.
Well, that’s it! I hope you’re having (or had) a scary Halloween. Mine was definitely made horrific by all the bugs I got to discover… or rediscover, along with the reality that they haven’t been fixed in the meanwhile.
The post CSS-ing Candy Ghost Buttons appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.
from CSS-Tricks https://ift.tt/3BzNBIV
via IFTTT
Saturday, October 30, 2021
Mostly Clear today!
With a high of F and a low of 30F. Currently, it's 38F and Clear outside.
Current wind speeds: 9 from the Northeast
Pollen: 2
Sunrise: October 30, 2021 at 07:17PM
Sunset: October 31, 2021 at 05:52AM
UV index: 0
Humidity: 76%
via https://ift.tt/2livfew
October 31, 2021 at 10:05AM
Friday, October 29, 2021
Clear today!
With a high of F and a low of 38F. Currently, it's 46F and Clear outside.
Current wind speeds: 10 from the Southwest
Pollen: 2
Sunrise: October 29, 2021 at 07:16PM
Sunset: October 30, 2021 at 05:53AM
UV index: 0
Humidity: 44%
via https://ift.tt/2livfew
October 30, 2021 at 10:04AM
Some Articles About Accessibility I’ve Saved Recently IV
- A guide to designing accessible, WCAG-compliant focus indicators — Sara Soueidan says you can make more accessible focus outlines by doing your own, rather than leaving it to the browser — as long as you do it right. Deep dive!
- a11y-syntax-highlighting — Eric Bailey’s repo of code syntax highlighting themes for a variety of software that are both attractive and meet WCAG color contrast guidelines (including support for Windows High Contrast mode as well).
- Respecting Users’ Motion Preferences — Michelle Barker with a guide on when and how to honor people’s
prefers-reduced-motion
setting. While there is no one right way, it should be done. It’s interesting how many things might apply. Properties liketransition
andanimation
are fairly obvious, but did you think ofscroll-behavior
or things specifically in JavaScript (where you can also test for a preference)? - Accessibility testing — Jeremy Keith: “When you commission an accessibility audit, you should hope to get feedback that’s mostly in that third category—interactive widgets.” Don’t waste an accessibility expert’s time telling you about color contrast problems because you can find and fix those yourself fairly easily.
- The effect of CSS on screen readers — Part of me wishes the effect of CSS on screen readers was “nothing” but Jozsef Polgar notes there are some things CSS does affect. A classic is setting
list-style: none
on lists will force them to not read as lists in VoiceOver. Jozsef points to Ben Meyer’s article covering similar ground. - In Quest of Search — Sara again, this time weighing in on
<search>
. I’ve heard mostly negative things (like, why the focus on this when there are so many bigger fish to fry, like<dialog>
andinert
), but Sara is all for it. I agree that we might as well have an element that gives us a freerole="search"
like we get with<nav>
and<main>
. - Accessible Palette: stop using HSL for color systems — Eugene Fedorenko says, “While HSL and HSV are fine choices for choosing a single color, they’re not suitable for building a color system, as they simply transform the RGB model and ignore the complexities of human perception.” It seems like everyone who has looked into next-gen color formats is a fan, but I haven’t wrapped my mind around them yet. Things like LCh, Lch(ab), HCL, LCH(uv)… There is starting to be some trickles of browser support.
- One last time: custom styling radio buttons and checkboxes — Scott O’Hara notes that you can entirely replace these inputs with custom styling, and do it accessibly. Part of doing it right is dealing with every possible state. Worth noting: without replacing everything, you can get pretty far in styling checkboxes/radios by just changing
width
/height
and theaccent-color
, as Dave and I noted here. - Understanding Logical Focus Order — Rachel Leggett explains it is “the idea that someone navigating your webpage with a keyboard (i.e. without a mouse) will encounter elements in an order that makes sense.” Nearly anytime you use the
order
property, you’re probably interferring here. But less obviously, hidden elements that remain interactive can confuse logical focus order. - Assistiv Labs — I didn’t realize there was a tool in the vein of CrossBrowserTesting or BrowserStack that allowed you to test in JAWS and NVDA and stuff — but there is! Just the other day I wanted to test in JAWS, so I spun up a (paid) copy of Parallels with my (paid) copy of Windows 10 and used my (40-minute test version) of JAWS to test. While it worked, it was not particularly cheap or without significant technical debt. This seems easier, although I haven’t actually tried it yet.
The post Some Articles About Accessibility I’ve Saved Recently IV appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.
from CSS-Tricks https://ift.tt/2Zxxbnc
via IFTTT
Thursday, October 28, 2021
Clear today!
With a high of F and a low of 30F. Currently, it's 34F and Clear outside.
Current wind speeds: 9 from the Northwest
Pollen: 2
Sunrise: October 28, 2021 at 07:15PM
Sunset: October 29, 2021 at 05:54AM
UV index: 0
Humidity: 57%
via https://ift.tt/2livfew
October 29, 2021 at 10:04AM
Okhsv and Okhsl
There is an old Russian fable where Okhsv and Okhsl are on a rowboat and Okhsv says to Okhsl, “What are the known shortcomings of HSL and HSV color pickers in design applications?” I kid, I kid. But really, what are they?
Björn Ottosson shows the two classics:
Despite color picking playing a big role in a lot of applications, the design of color pickers isn’t a particularly well researched topic. While some variation exist in the widgets themselves, the choice of HSL or HSV is mostly taken for granted, with only a few exceptions.
Is their dominance well deserved or would it be possible to create better alternatives?
It’s all rather above my head, but I certainly support the idea of researching and exploring things that so many of us just take for granted. There is a whole playground comparing different possibilities. It’s not so much about the UI/UX of the picker itself, but how the range of colors is expressed in the picker area.
Direct Link to Article — Permalink
The post Okhsv and Okhsl appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.
from CSS-Tricks https://ift.tt/3hh1yEq
via IFTTT
Vite + _____
Vite, “Next Generation Frontend Tooling” from Evan You, has been capturing a lot of attention. I’ve heard rave reviews from developers, even in private chats (you gotta try this!). Being from Evan, Vite works great with Vue, but Vue doesn’t seem to be the only first-class citizen of Vite. The plugins support Vue and React just the same and it looks like configurations for lit, Preact, and Svelte are easy.
It’s interesting to see other technologies try to get on the bandwagon, almost certainly for the wicked speed. I believe it uses esbuild, which is known for speed, for… some things?… under the hood, but not bundling. Just noting some of this bandwagon stuff happening like…
- Sam Richard making Eleventy work with Vite.
- Ben Holmes made Slinkity, which “duct-tapes” (see Smashing article) Eleventy and Vite together.
- Michael Shilman makes Storybook work with Vite.
- Fred K. Schott notes that Astro has moved to Vite.
- Anthony Fu has a “starter pack” called Vitesse that smashes together lots of opinionated tools all together with Vite.
- There are sandboxes starting to use it, like StackBlitz, and Replit is clearly stoked about it.
- Vue-specific projects are moving to it, for example a Vite-specific Nuxt and Vite-specific VuePress.
Hey, people like speed and good DX. Always worth noting when you see this much movement.
The post Vite + _____ appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.
from CSS-Tricks https://ift.tt/2ZsVmD6
via IFTTT
Merge Conflicts: What They Are and How to Deal with Them
This article is part of our “Advanced Git” series. Be sure to follow Tower on Twitter or sign up for their newsletter to hear about the next articles.
Merge conflicts… Nobody likes them. Some of us even fear them. But they are a fact of life when you’re working with Git, especially when you’re teaming up with other developers. In most cases, merge conflicts aren’t as scary as you might think. In this fourth part of our “Advanced Git” series we’ll talk about when they can happen, what they actually are, and how to solve them.
Advanced Git series:
- Part 1: Creating the Perfect Commit in Git
- Part 2: Branching Strategies in Git
- Part 3: Better Collaboration With Pull Requests
- Part 4: Merge Conflicts (You are here!)
- Part 5: Rebase vs. Merge (Coming soon!)
- Part 6: Interactive Rebase
- Part 7: Cherry-Picking Commits in Git
- Part 8: Using the Reflog to Restore Lost Commits
How and when merge conflicts occur
The name gives it away: a merge conflict can occur when you integrate (or “merge”) changes from a different source into your current working branch. Keep in mind that integration is not limited to just merging branches. Conflicts can also happen during a rebase or an interactive rebase, when you’re cherry picking in Git (i.e. when you choose a commit from one branch and apply it to another), when you’re running git pull
or even when reapplying a stash.
All of these actions perform some kind of integration, and that’s when merge conflicts can happen. Of course, this doesn’t mean that every one of those actions results in a merge conflict every time — thank goodness! But when exactly do conflicts occur?
Actually, Git’s merging capabilities are one of its greatest advantages: merging branches works flawlessly most of the time because Git is usually able to figure things out on its own and knows how to integrate changes.
But there are situations where contradictory changes are made — and that’s when technology simply cannot decide what’s right or wrong. These situations require a decision from a human being. For example, when the exact same line of code was changed in two commits, on two different branches, Git has no way of knowing which change you prefer. Another situation that is a bit less common: a file is modified in one branch and deleted in another one. Git will ask you what to do instead of just guessing what works best.
How to know when a merge conflict has occurred
So, how do you know a merge conflict has occurred? Don’t worry about that — Git will tell you and it will also make suggestions on how to resolve the problem. It will let you know immediately if a merge or rebase fails. For example, if you have committed changes that are in conflict with someone else’s changes, Git informs you about the problem in the terminal and tells you that the automatic merge failed:
$ git merge develop
CONFLICT (content): Merge conflict in index.html
Automatic merge failed; fix conflicts and then commit the result.
You can see that I ran into a conflict here and that Git tells me about the problem right away. Even if I had missed that message, I am reminded about the conflict the next time I type git status
.
If you’re working with a Git desktop GUI like Tower, the app makes sure you don’t overlook any conflicts:
In any case: don’t worry about not noticing merge conflicts!
How to undo a merge conflict and start over
You can’t ignore a merge conflict — instead, you have to deal with it before you can continue your work. You basically have the following two options:
- Resolve the conflict(s)
- Abort or undo the action that caused the conflict(s)
Before we go into resolving conflicts, let’s briefly talk about how to undo and start over (it’s very reassuring to know this is possible). In many cases, this is as simple as using the --abort
parameter, e.g. in commands like git merge --abort
and git rebase --abort
. This will undo the merge/rebase and bring back the state before the conflict occurred.
This also works when you’ve already started resolving the conflicted files and, even then, when you find yourself at a dead end, you can still undo the merge. This should give you the confidence that you really can’t mess up. You can always abort, return to a clean state, and start over.
What merge conflicts really look like in Git
Let’s see what a conflict really looks like under the hood. It’s time to demystify those little buggers and get to know them better. Once you understand a merge conflict, you can stop worrying.
As an example, let’s look at the contents of an index.html
file that currently has a conflict:
Git is kind enough to mark the problematic areas in the file. They’re surrounded by <<<<<<<
and >>>>>>>
. The content after the first marker originates from our current working branch (HEAD
). The line with seven equals signs (=======
) separates the two conflicting changes. Finally, the changes from the other branch are displayed (develop
in our example).
Your job is to clean up those lines and solve the conflict: in a text editor, in your preferred IDE, in a Git desktop GUI, or in a Diff & Merge Tool.
How to solve a conflict in Git
It doesn’t matter which tool or application you use to resolve a merge conflict — when you’re done, the file has to look exactly as you want it to look. If it’s just you, you can easily decide to get rid of a code change. But if the conflicting change comes from someone else, you might have to talk to them before you decide which code to keep. Maybe it’s yours, maybe it’s someone else’s, and maybe it’s a combination of those two.
The process of cleaning up the file and making sure it contains what you actually want doesn’t have to involve any magic. You can do this simply by opening your text editor or IDE and making your changes.
Sometimes, though, you’ll find that this is not the most efficient way. That’s when dedicated tools can save time and effort. For example, there are various Git desktop GUIs which can be helpful when you’re resolving merge conflicts.
Let’s take Tower as an example. It offers a dedicated “Conflict Wizard” that makes these otherwise abstract situations more visual. This helps to better understand where the changes are coming from, what type of modification occurred, and ultimately solve the situation:
Especially for more complicated conflicts, it can be great to have a dedicated Diff & Merge Tool at hand. It can help you understand diffs even better by offering advanced features like special formatting and different presentation modes (e.g. side-by-side, combined in a single column, etc.).
There are several Diff & Merge Tools on the market (here are some for Mac and for Windows). You can configure your tool of choice using the git config
command. (Consult the tool’s documentation for detailed instructions.) In case of a conflict, you can invoke it by simply typing git mergetool
. As an example, I’m using the Kaleidoscope app on my Mac:
After cleaning up the file — either manually in a text editor, in a Git desktop GUI, or with a Merge Tool — you can commit the file like any other change. By typing git add <filename>
, you inform Git that the conflict has been resolved.
When all merge conflicts have been solved and added to the Staging Area, you simply create a regular commit. And this completes the conflict resolution.
Don’t panic!
As you can see, a merge conflict is nothing to worry about and certainly no reason to panic. Once you understand what actually happened to cause the conflict, you can decide to undo the changes or resolve the conflict. Remember that you can’t break things — even if you realize you made a mistake while resolving a conflict, you can still undo it: just roll back to the commit before the great catastrophe and start over again.
If you want to dive deeper into advanced Git tools, feel free to check out my (free!) “Advanced Git Kit”: it’s a collection of short videos about topics like branching strategies, Interactive Rebase, Reflog, Submodules and much more.
Advanced Git series:
- Part 1: Creating the Perfect Commit in Git
- Part 2: Branching Strategies in Git
- Part 3: Better Collaboration With Pull Requests
- Part 4: Merge Conflicts (You are here!)
- Part 5: Rebase vs. Merge (Coming soon!)
- Part 6: Interactive Rebase
- Part 7: Cherry-Picking Commits in Git
- Part 8: Using the Reflog to Restore Lost Commits
The post Merge Conflicts: What They Are and How to Deal with Them appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.
from CSS-Tricks https://ift.tt/2ZslRsj
via IFTTT
Building an Angular Data Grid With Filtering
(This is a sponsored post.)
Kendo UI makes it possible to go from a basic idea to a full-fledged app, thanks to a massive component library. We’re talking well over 100 components that are ready for you to drop into your app at will, whether it’s React, Angular or Vue you’re working in — they just work. That is because Kendo UI is actually a bundle of four JavaScript libraries, each built natively for their respective framework. But more than that, as we’ve covered before, the components are super themeable to the extent that you can make them whatever you want.
But here’s the real kicker with Kendo UI: it takes care of the heavy lifting. The styling is great and all, but what separates Kendo UI from other component frameworks is the functionality it provides right out of the box.
Case in point: data. Rather than spending all your time figuring out the best way to bind data to a component, that’s just a given which ultimately allows you to focus more of your time on theming and getting the UI just right.
Perhaps the best way to see how trivial Kendo UI makes working with data is to see it in action, so…
Let’s look at the Angular Grid component
This is the Kendo UI for Angular Data Grid component. Lots of data in there, right? We’re looking at a list of employees that displays a name, image, and other bits of information about each person.
Like all of Kendo UI’s components, it’s not like there’s one data grid component that they square-pegged to work in multiple frameworks. This data grid was built from scratch and designed specifically to work for Angular, just as their KendoReact Grid component is designed specifically for React.
Now, normally, a simple <table>
element might do the trick, right? But Kendo UI for Angular’s data grid is chockfull of extras that make it a much better user experience. Notice right off the bat that it provides interactive functionality around things like exporting the data to Excel or PDF. And there are a bunch of other non-trivial features that would otherwise take a vast majority of the time and effort to make the component.
Filtering
Here’s one for you: filtering a grid of data. Say you’re looking at a list of employees like the data grid example above, but for a company that employees thousands of folks. It’d be hard to find a specific person without considering a slew of features, like search, sortable columns, and pagination — all of which Kendo UI’s data grid does.
Users can quickly parse the data bound to the Angular data grid. Filtering can be done through a dedicated filter row, or through a filter menu that pops up when clicking on a filter icon in the header of a column.
Kendo UI’s documentation is great. Here’s how fast we can get this up and running.
First, import the component
No tricks here — import the data grid as you would any other component:
import { Component, OnInit, ViewChild } from '@angular/core';
import { DataBindingDirective } from '@progress/kendo-angular-grid';
import { process } from '@progress/kendo-data-query';
import { employees } from './employees';
import { images } from './images';
Next, call the component
@Component({
selector: 'my-app',
template: `
<kendo-grid>
// ...
</kendo-grid>
`
})
This is incomplete, of course, because next we have to…
Configure the component
The key feature we want to enable is filtering, but Kendo’s Angular Grid takes all kinds of feature parameters that can be enabled in one fell swoop, from sorting and grouping, to pagination and virtualization, to name a few.
Filtering? It’s a one-liner to bind it to the column headers.
@Component({
selector: 'my-app',
template: `
<kendo-grid
[kendoGridBinding]="gridView"
kendoGridSelectBy="id"
[selectedKeys]="mySelection"
[pageSize]="20"
[pageable]="true"
[sortable]="true"
[groupable]="true"
[reorderable]="true"
[resizable]="true"
[height]="500"
[columnMenu]="{ filter: true }"
>
// etc.
</kendo-grid>
`
})
Then, mark up the rest of the UI
We won’t go deep here. Kendo UI’s documentation has an excellent example of how that looks. This is a good time to work on the styling as well, which is done in a styles
parameter. Again, theming a Kendo UI component is a cinch.
So far, we have a nice-looking data grid before we even plug any actual data into it!
And, finally, bind the data
You may have noticed right away when we imported the component that we imported “employee” data in the process. We need to bind that data to the component. Now, this is where someone like me would go run off in a corner and cry, but Kendo UI makes it a little too easy for that to happen.
// Active the component on init
export class AppComponent implements OnInit {
// Bind the employee data to the component
@ViewChild(DataBindingDirective) dataBinding: DataBindingDirective;
// Set the grid's data source to the employee data file
public gridData: any[] = employees;
// Apply the data source to the Grid component view
public gridView: any[];
public mySelection: string[] = [];
public ngOnInit(): void {
this.gridView = this.gridData;
}
// Start processing the data
public onFilter(inputValue: string): void {
this.gridView = process(this.gridData, {
filter: {
// Set the type of logic (and/or)
logic: "or",
// Defining filters and their operators
filters: [
{
field: 'full_name',
operator: 'contains',
value: inputValue
},
{
field: 'job_title',
operator: 'contains',
value: inputValue
},
{
field: 'budget',
operator: 'contains',
value: inputValue
},
{
field: 'phone',
operator: 'contains',
value: inputValue
},
{
field: 'address',
operator: 'contains',
value: inputValue
}
],
}
}).data;
this.dataBinding.skip = 0;
}
// ...
}
Let’s see that demo again
That’s a heckuva lot of power with a minimal amount of effort. The Kendo UI APIs are extensive and turn even the most complex feature dead simple.
And we didn’t even get to all of the other wonderful goodies that we get with Kendo UI components. Take accessibility. Could you imagine all of the consideration that needs to go into making a component like this accessible? Like all of the other powerful features we get, Kendo UI tackles accessibility for us as well, taking on the heavy lifting that goes into making a keyboard-friendly UI that meets WCAG 2.0 Alice standards and is compliant with Section 508 and WAI-ARIA standards.
The post Building an Angular Data Grid With Filtering appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.
from CSS-Tricks https://ift.tt/3bgzxcJ
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...
-
So you want an auto-playing looping video without sound? In popular vernacular this is the very meaning of the word GIF . The word has stuck...
-
With a high of F and a low of 31F. Currently, it's 37F and Cloudy outside. Current wind speeds: 7 from the Northeast Pollen: 0 S...
-
Last year , we kicked out a roundup of published surveys, research, and other findings from around the web. There were some nice nuggets in ...