Line-by-line: avancerede CSS-tricks til klik for at åbne rullelister og menuer

Så længe jeg kan huske, var der altid to slags vælgere.

Der var den slags, hvor teksten på toppen kunne redigeres, og den slags, hvor den ikke kunne. HTML inkluderer den anden slags, ikke noget problem:

 Apple Banana Cherry Dewberry 

Men jeg var chokeret over at høre, at den første slags ikke findes i HTML. Åh, der er en ting, der hedder a datalist, men det fungerer ikke rigtigt - brugere kan ikke klikke på noget for at se hele listen, og når du begynder at skrive, begynder emner straks at forsvinde, hvis de ikke starter med den samme streng som den bruger indtastet.

Men CSS er et stylingværktøj med imponerende kraft: hele videospil er bygget ud af CSS, HTML og et par billedfiler. (Fantastisk, jeg mistede lige halvdelen af ​​mit publikum.)

Dette betyder ikke, at CSS kan gøre noget , men det betyder, at der i det mindste er "hacks" til at udføre en bred vifte af tricks. De af jer, der er færdige med at spille spil, er sandsynligvis interesseret i at lære om nogle af tricksne i handelen, og jeg tror, ​​der er meget at lære ved at finde ud af, hvordan man laver en kombinationsboks.

I denne artikel lærer du, hvordan denne ting fungerer:

I Windows kalder vi dem ”kombinationsbokse”, da de kombinerer en øverste del (normalt et tekstfelt) med en popup-del (normalt en rulleliste).

Brugsvejledning

Kombinationsboksen kan konstrueres ud af divs og / eller spændvidder. Husk bare, at en HTML-parser har et par indlejringsregler. For eksempel tillader det ikke pat være en forfader til diveller ulog spankan ikke være en forfader til peller div. (Disse regler gælder ikke for JavaScript / React-kode, der redigerer DOM)

CSS forventer tre børn: først den øverste del (indholdet skal altid vises), derefter for pil ned og endelig indholdet, der skal vises inde i rullemenuen:"downarrow" tabindex="-1">

 Simple combo box Contents of drop-down popup go here 

Som standard åbnes rullemenuen kun, når der klikkes på pil ned (▾). For at åbne feltet, når der klikkes på det øverste indhold, skal du føje dropdownklassen til comboboxog tilføje en tabindex="0"attribut til det første barn:

 Simple combo box Contents of drop-down popup go here 

Bemærk:tabindex="-1" betyder "du kan klikke for at give det fokus, men du kan ikke fokusere det ved hjælp af Tab på tastaturet". tabindex="0"betyder "du kan fokusere det med et klik eller med tabulatortasten, og browseren vælger den rækkefølge, som forskellige elementer er fokuseret med Tab-tasten." I modsætning til etelement kan popup-boksen ikke gå uden for browservinduet (dette kan være en forsætlig begrænsning af alt brugerdefineret indhold - hvis brugerdefineret indhold kan strække sig ud over kanten af ​​sideområdet, kan der være en sikkerhedsrisiko ved, at websteder forsøger at forvirre eller narre brugere.)

Som en bonus kan du oprette en rulleliste, der ikke er en kombinationsboks med dropdownklassen alene:

 *** Dropdown menu *** Contents of drop-down popup go here 

Dette er beregnet som en klik-for-at-åbne rullemenu (hvis du vil have en rullemenu, der åbnes med musen i stedet for museklik, er der allerede mange andre vejledninger om det.)

I dette tilfælde indeholder det sidste element rullemenuen, og alle andre børn er altid synlige, men kun elementer med en tabindexattribut kan klikkes for at åbne popup-området.

Du kan sikkert redigere margenen og kanten af ​​en kombinationsboks og dens børn uden at ødelægge dens adfærd, undtagen en ting: lad ikke padding-rightblive for lille, fordi pilen ▾ vises i polstringen - dens størrelse skal være mindst 1em.

Resumé

  • Den comboboxklasse er for en kombinationsboks
  • Den dropdownklasse er for menuer og kombinationsbokse, der falder ned, når den øverste indholdet klikkes (husk tabindex="0")
  • Den downarrowklasse tilføjer ned-pil ( tabindex="-1"er påkrævet, fordi det ikke kan tilføjes via CSS.)
  • Det sidste barn til comboboxeller dropdowner dropdown-indholdet.

Og du kan forhåndsvise demoen med kildekoden.

CSS-funktioner, vi har brug for

Vi har brug for en masse ting til dette. Her er en liste (spring over og læs senere senere).

Vælgere

Grundlæggende vælgere:

.abetyder "matche elementer med class='a'".

A, Bbetyder "matchvælger Aeller vælger B".

A Bbetyder "match et Belement, der har et Aelement som en forfader".

A > B betyder "matche et B-element, hvis forælder er et A-element".

:first-child pseudovælger:

*:first-child betyder "match ethvert element, så længe det er det første barn til et eller andet forældreelement".

:last-child pseudovælger:

*:last-childbetyder "match ethvert element, så længe det er det sidste barn i et andet element". .combobox > *:last-childFinder for eksempel det sidste barn i ethvert element med class="combobox".

:empty pseudovælger:

.downarrow:emptybetyder "matche et element med, class="downarrow"hvis det ikke har noget i det (ikke engang almindelig tekst)".

:only-child pseudovælger:

*:only-child betyder "match ethvert element, hvis det er det eneste barn til et andet element".

:not pseudovælger:

.dropdown:not(.sticky)betyder "match et element med dropdownklassen, hvis det ikke har stickyklassen".

:focus pseudovælger:

.downarrow:focusbetyder "match et element med downarrowklassen, hvis det har fokus, fordi det har et, tabindexog det blev klikket med musen eller valgt med Tab".

:hover pseudovælger:

.foo:hoverbetyder "match et element med fooklassen, når musemarkøren er oven på den".

A ~ Bbetyder "match, Bhvis et tidligere søskende matchede A".

Styles

Basic styles:

Make sure you understand the box model and its various associated styles (including width, height, min-width and max-height) before you continue. You should also know about other basic styles like font-size, font-family, color, and background-color.

You should also know about units, especially the most common units:

px, em, rem, and %.

box-sizing: border-box style

This means that the width and height of an element includes the padding and border.

display: style

We’ll be using display: block, which displays an element as a “block”, which is like a paragraph in that two adjacent blocks have line breaks between them.

We will also use display: inline-block, which displays an element inline, like an icon image within a paragraph, but still allows margins, borders and padding.

We will not explicitly use display: inline, which is used for elements that do not have margins, borders or padding and do not need line breaks between them (like this).

Learn more about display.

position: style

In the combo box, we will see how this style is used to take elements out of the normal document flow.

Elements normally have a style of position: static, which just means “position it on the page normally”.

position: relative is like static, except two things: first, the element can be shifted left, right, up or down without affecting any other elements.

However, the combo box doesn’t need this feature. The second effect of relative is to mark the element as “positioned”.

This matters because another position, absolute, positions an element relative to its nearest “positioned” ancestor. Specifically, the drop-down popup will use position: absolute in order to position itself relative to the top part of the combo box — therefore the combo box itself is marked relative.

Also, an absolute element doesn’t affect the positioning of other items on the page, not even its own parent element, and that’s just what we want for a popup box.

left, top, right and bottom styles

These styles are used with position: relative and position: absolute, and they work a little differently for each one. More on that later.

Learn more about positioning.

outline:style

Outline is an extra border drawn outside an element’s normal border. It is normally used to highlight an element, like to indicate it has been “selected” by a user. Because outlines are expected to be temporary, they don’t occupy space on the page — so adding an outline won’t push other elements out of the way.

box-shadow:style

Draws a shadow “under” the element (well, actually the shadow is drawn outside the element, which looks very strange if the element has no background). This will be handy for the drop-down popup!

z-index:style

This style changes the order in which an element is drawn by the browser. A higher z-index causes an element to be drawn later so that it appears to be above other things on the page.

We’ll need a large z-index for our drop-down popup so that it appears on top of everything else. The children of the popup will get a new “stacking context”, which basically means they will automatically be drawn on top of the popup, which is good.

Caution: z-index only works on “positioned” elements.

cursor:style

Controls the mouse cursor’s appearance.

text-align:style

Horizontal text justification (left, right or center).

pointer-events:style

This style’s none setting makes an element “invisible” to mouse clicks.

transform:style

Allows you to rotate, scale, skew, or translate a block (or inline-block) element. These transforms are smart and affect mouse input also.

For example, you could rotate text 30 degrees and still select it with the mouse.

transition:style

Enables animation when styles change.

opacity: style

A number between 0 and 1 controls how easy an element is to see:

1 is the normal value which makes an element fully visible

0 makes an element completely invisible. (Unlike visibility: hidden and display: none, the other ways of making something invisible, opacity: 0 does not prevent the mouse from interacting with the element.)

In this article, we will use opacity for animation — by animating the transition between opacity: 0 and opacity: 1, we can make an element fade in or out.

Pseudo-element

::before or ::after:

Refers to a virtual element within an element previously selected, before or after its normal content.

For example if you write p::before { content: "!" } then ! will appear at the beginning of every paragraph.

We can use content with ::before or ::after to draw the down arrow (▾).

Preparing the initial appearance

.combobox and .dropdown need to be relative so that the drop-down popup can be positioned relative to them. display: inline-block allows the combo box to have margins, padding and border. Unlike display: block it allows other things to appear on the same line (such as labels or other combo boxes.)

.combobox, .dropdown { /* "relative" and "inline-block" (or just "block") are needed here so that "absolute" works correctly in children */ position: relative; display: inline-block; }

Combo boxes, but not drop-down lists, will have a built-in border:

.combobox { border: 1px solid #999; padding-right: 1.25em; /* leave room for ▾ */ }

The color #999 is slightly darker than the border on Chrome’s element, and slightly lighter than FireFox's element, so it doesn't look too much different than either of them.

How do we draw the little down arrow (▾)?

The difficulty here is controlling its height. The combo box might have content of an unpredictable size: small font, large font, one line or two lines. The arrow “button” needs to have the same height so that it works no matter where the user clicks on it — anywhere within the border should work.

So, how can we make the arrow adapt to the height of its left sibling?

CSS grid can accomplish this straightforwardly, but it is not supported by all browsers. Perhaps Flexbox could do the job too, but I decided to use an old trick for compatibility with older browsers: absolute positioning.

With absolute positioning, I can force the arrow to have the same height as its container.

The disadvantage of this approach is that the arrow will exist outside the normal flow of the document, so the browser won’t reserve any space for it. Instead, we will give the combo box some padding on the right side (1.25em above), and the arrow will live within the padding.

In absolute positioning mode, top aligns the top edge of an element relative to the top edge of its container: top: 0 means the two top edges will be at the same location. Similarly left: 0 aligns the left side of the element to the left side of the container, and so on.

Positive coordinates push the element “inward” relative to the container, so top: 10px means “put the top of the element 10px down from the top of the parent”, while bottom: 10px means “put the bottom of the element 10px up from the bottom of the parent.”

In this case we need top: 0; bottom: 0; right: 0; width: 1.25em to put the arrow on the right side, top-to-bottom.

.combobox > .downarrow, .dropdown > .downarrow { display: block; /* Allow margin/border/padding/size */ position: absolute; /* Outside normal flow */ top: 0; /* Align top of downarrow with top edge of combobox */ bottom: 0; /* Align bottom of downarrow with bottom of combobox */ right: 0; /*Align right edge of downarrow with right of combobox*/ width: 1.25em; cursor: default; /* Use arrow cursor instead of I-beam */ nav-index: -1; /* sets tabindex, nonfunctional in most browsers */ border-width: 0; /* disable by default */ border-color: inherit; /* copy parent border */ border-left: inherit; /* copy parent border */ }

Here, display: block and display: inline-block have the same effect, so I used the shorter one. I also disabled the I-beam mouse cursor normally shown over text (since the down arrow counts as text).

There is actually a way to set tabindex in CSS, it’s called nav-index. But most browsers don’t support it, so if you find that your combo box only works in Opera, you know why.

You must therefore add tabindex="-1" beside class="downarrow".

This code disables the borders, with the caveat that the border color/style should be inherited from the parent element (the combo box) if other CSS increases border-left-width. You can use the inherit option on any attribute that doesn’t inherit from parent by default, by the way.

I decided there should be a left border if the popup won’t open when the left side is clicked. That way, the drop-down arrow looks like a button, subtly suggesting it can be clicked. Remember the plan: only dropdown, not combobox alone, will open when the left side is focused.

Therefore I will add a border when combobox alone is used:

.combobox:not(.dropdown) > .downarrow { border-left-width: 1px; }

Next, if the user has provided us with an empty , we need to magically add the missing down arrow character using ::before (or ::after) and content:

.downarrow:empty::before { content: '▾'; }

The down arrow needs to be centered within the .downarrow element, too. text-align: center will center the text horizontally, but vertical centering is tricky. vertical-align: middle doesn’t work, because it is designed to align inline elements with the surrounding text. What we want is to align our down arrow pseudo-element with the parent.downarrow container.

There’s a trick to it:

.downarrow::before, .downarrow > *:only-child { text-align: center; /* Center horizontally */ /* vertical centering trick */ position: relative; /* Allow the element to be shifted */ top: 50%; /* Move down by 50% of container size */ transform: translateY(-50%); /* Move up by 50% of element size */ display: block; /* `transform` requires block/inline-block */ }

Remember that we add the ::before content only if the .downarrow is empty. If the user has provided their own custom down arrow element, we still want to center it, hence the .downarrow > *:only-child selector.

And if the combo box contains an element, it shouldn’t have a border:

.combobox > input { border: 0 /* combo box already has a border */ }

This next part is optional, but usually the first child of a combo box should have a width of 100% of its parent .combobox so that if the combo box is wider than its first child, the first child stretches to match. And in case the user constructed the combo box out of spans rather than divs (perhaps so it could be placed within a

), it may make sense to set the first child as inline-block so it can have padding and margins.

.combobox > *:first-child { width: 100%; box-sizing: border-box; /* so 100% includes border & padding */ display: inline-block; }

Preparing the drop-down list

Initially we just want it hidden, so we can use display: none.

But in preparation for when it is visible, let’s set some other properties too. Start with position: absolute so it’s outside the normal document flow (remember that an absolute element is positioned relative to its nearest relative ancestor, which is .combobox or .dropdown). When displayed, it should have a border and a background, of course, and also a shadow underneath it.

Here you see box-shadow: 1px 2px 4px 1px #4448, which means “show a shadow 1px to the right of the element, 2px downward, blurred by 4px, and make the shadow 1px larger than the element itself, with a color of #4448”. We also need a nice big z-index so the popup will appear on top of everything else:

.dropdown > *:last-child, .combobox > *:last-child { display: none; /* hidden by default */ position: absolute; /* outside document flow */ left: 0; /* Left side of popup = left side of parent */ top: 100%; /* Top of popup = 100% below top of parent */ border: 1px solid #999; /* gray border */ background-color: #fff; /* white background */ box-shadow: 1px 2px 4px 1px #4448; /* shadow behind */ z-index: 9999; /* draw on top of everything else */ min-width: 100%; /* >= 100% as wide as its container */ box-sizing: border-box; /* width includes border & padding */ }

Here I’ve used left: 0 and top: 100% to position the popup correctly, but in this case it turns out that the default position of the popup is practically the same, so these styles aren’t really necessary.

To make the drop-down box visible, all we really need is display: block.

But which selectors do we need to make that happen?

??? { display: block; }

Most obviously, the drop-down should be shown in these three cases.

  1. The user clicked the .downarrow
  2. The user clicked or tabbed to .dropdown
  3. The user clicked or tabbed to a child of .dropdown

The drop-down box is the last child, so we’ll need to combine the *:last-child selector with :focus to detect when one of the above things has been clicked or tabbed-to:

.combobox > .downarrow:focus ~ *:last-child, .dropdown:focus > *:last-child, .dropdown > *:focus ~ *:last-child { display: block; }

We’re not done yet, though. What if the user clicks a text box or a link inside the drop-down box? The click will cause the .downarrow or the .dropdown to lose the focus, causing the drop-down box to disappear instantly.

In the case of a link, the browser focuses the link when the mouse button goes down but it does not follow the link until the mouse button is released. So if the drop-down disappears instantly, any links in the drop-down cannot be followed!

To fix this, we should keep the box open whenever something within the :last-child has the focus:

.combobox > .downarrow:focus ~ *:last-child, .dropdown:focus > *:last-child, .dropdown > *:focus ~ *:last-child, .combobox > *:last-child:focus-within, .dropdown > *:last-child:focus-within { display: block; }

Caution: This doesn’t work in Edge/IE (a workaround is described below).

If the down-arrow is clicked a second time, we should hide the drop-down box. This can be accomplished like so:

.downarrow:focus { pointer-events: none; /* Causes second click to close */ }

This causes the .downarrow to be invisible to mouse events when it has the focus, so that when you click it, you are actually clicking what is behind it (the .combobox). This causes it to lose the focus, which in turn causes the drop-down box to disappear.

We can do the same thing for .dropdown, so clicking the top area of a .dropdown again makes it disappear:

.dropdown > *:not(:last-child):focus, .downarrow:focus, .dropdown:focus { pointer-events: none; /* Causes second click to close */ }

This mostly works. But if your top area contains a text box, there is a side effect since the text box won’t process mouse input normally. However, I have found that the text box is still usable.

In Firefox you can click and drag to select text if you start when the popup is closed, but it doesn’t work when the popup is open. In Edge it’s the opposite: you can click and drag to select text only when the popup is open. Either way, it’s basically usable since the user is likely to retry once if his input doesn’t work the first time.

Chrome’s behavior is… inconsistent. In any case, to get perfect behavior — where a click closes the box without causing the text box to lose focus — I think JavaScript is required.

Finishing touches

The combo box should normally have a margin. But this seems optional, since controls don’t have one by default:

.combobox { margin: 5px; }

Let’s make this thing cooler by opening the box with animation.

The transition property is the easiest way to do animations. In fact, for our purposes, a simple command like transition: 0.4s; enables animations for all supported styles. But so far the only style we are changing is display, and changes to display cannot be animated.

So let’s try animating a transition from opacity: 0 to opacity: 1 by modifying our existing styles…

.dropdown > *:last-child, .combobox > *:last-child { display: none; /* ... other styles same as before ... */ opacity: 0; transition: 0.4s; } .combobox > .downarrow:focus ~ *:last-child, .dropdown:focus > *:last-child, .dropdown > *:focus ~ *:last-child, .combobox > *:last-child:focus-within, .dropdown > *:last-child:focus-within { display: block; opacity: 1; transition: 0.15s; }

The time on the transition controls how long it takes to enter the current state. So this code should mean “take 0.15 seconds to show and 0.4 seconds to hide.”

But the animation doesn’t work. It turns out that display: hidden blocks animations. Instead we need to use one of the other ways of hiding things. Another way to hide things is with visibility: hidden. Unfortunately, this partially blocks animations, too — the animation for showing the popup works, but the animation for hiding the popup doesn’t.

We can’t rely on opacity: 0by itself to hide an element, because the mouse can still interact with an element that has opacity: 0. However, we can fix this with pointer-events: none.

So the working fade-in and fade-out looks like this:

.dropdown > *:last-child, .combobox > *:last-child { display: block; /* ... other styles same as before ... */ transition: 0.4s; opacity: 0; pointer-events: none; } .combobox > .downarrow:focus ~ *:last-child, .dropdown:focus > *:last-child, .dropdown > *:focus ~ *:last-child, .combobox > *:last-child:focus-within, .dropdown > *:last-child:focus-within { display: block; transition: 0.15s; opacity: 1; pointer-events: auto; }

Another flourish we could add is to move the popup into position, like by animating top:

.dropdown > *:last-child, .combobox > *:last-child { display: block; /* ... other styles same as before ... */ top: 0; opacity: 0; transition: 0.4s; pointer-events: none; } .combobox > .downarrow:focus ~ *:last-child, .dropdown:focus > *:last-child, .dropdown > *:focus ~ *:last-child, .combobox > *:last-child:focus-within, .dropdown > *:last-child:focus-within { display: block; top: 100%; opacity: 1; transition: 0.15s; pointer-events: auto; }

I decided this was a bit “over the top” and did not include it in the final version.

Finally, we should have a focus rectangle — a border showing when the combo box is “active”.

First let’s add a focus rectangle for that down arrow:

.downarrow:focus { outline: 2px solid #48F8; }

Ideally we would have a focus rectangle for the combo box itself, like this:

.combobox:focus-within { outline: 2px solid #48F; }

This works fine in Chrome. But in Firefox 61 the outline is expanded beyond the border to enclose the entire popup box also, which looks a little odd, especially if the popup box doesn’t have the same width as the top part. In Edge the outline doesn’t show up at all because Edge doesn’t support :focus-within (see below). So, what can we do instead?

I decided to use this:

.combobox > *:not(:last-child):focus { outline: 2px solid #48F8; }

This draws an outline around the focused child instead of the combo box itself. But this sometimes looks odd too, if the child is not the same size as the enclosing combo box. So I added transparency (#48F8 instead of #48F) to make it less visible, and therefore less odd-looking in the worst case.

Stickiness

The styles we have so far keep the box open only when something is focused. So if you click on plain text in the popup area, the popup closes. For the final version I expanded the list of reasons to keep the popup open to include a sticky style that will keep the drop-down open on mouse hover, so that clicking doesn’t close the box

.combobox > .downarrow:focus ~ *:last-child, .dropdown:focus > *:last-child, .dropdown > *:focus ~ *:last-child, .combobox > *:last-child:focus-within, .dropdown > *:last-child:focus-within, .combobox > .sticky:last-child:hover, .dropdown > .sticky:last-child:hover { display: block; top: 100%; opacity: 1; transition: 0.15s; pointer-events: auto; }

As I discussed earlier, glitches occur when the top area of a combo box contains a text box. To let you easily avoid this problem, I tweaked the existing CSS so that the pointer-events: none style is not applied if the .dropdown element also has the sticky class:

.dropdown:not(.sticky) > *:not(:last-child):focus, .downarrow:focus, .dropdown:focus { pointer-events: none; /* Causes second click to close */ }

Finally, if a .dropdown list contains links, there is a small inconvenience. After clicking a link, the list will not close automatically since the link has the focus and we programmed the drop-down not to close when a child has the focus.

To avoid this I added support for a new less-sticky class. Like sticky, less-sticky keeps the popup open when the mouse hovers over it. Unlike sticky, less-sticky does not keep the popup open when a child has the focus.

So our new list of selectors is getting pretty long:

.combobox > .downarrow:focus ~ *:last-child, .dropdown:focus > *:last-child, .dropdown > *:focus ~ *:last-child, .combobox > .sticky:last-child:hover, .dropdown > .sticky:last-child:hover, .combobox > .less-sticky:last-child:hover, .dropdown > .less-sticky:last-child:hover, .combobox > *:last-child:focus-within:not(.less-sticky), .dropdown > *:last-child:focus-within:not(.less-sticky) { display: block; opacity: 1; transition: 0.15s; pointer-events: auto; top: 100%; }

And we’re not even done yet, because this is not compatible with Edge and Internet Explorer yet.

Edge Cases

Once I got my combo box working perfectly in Firefox and Chrome, I was dismayed to see it completely ugly and unusable in Edge. What went wrong?

First, the borders were gone because Edge and IE don’t support opacity on borders, as in rgb(200,150,100,50) or #8888. I had used #8888 as the border. To make it work on Edge, I changed it to #999.

Another alternative is to offer a non-opaque border just for Edge:

border: 1px solid #888; /* Edge/IE can't do border opacity */ border: 1px solid #8888; /* All other browsers */

Second, click as I might — the down-dropping-divs just wouldn’t drop down!

In solving this issue, I learned something new — if a browser doesn’t understand a selector used in a CSS declaration, it will ignore the entire block.

For instance if you write .x, .y, .z:unknown { margin:1em }, then x and y won’t get margins simply because the browser doesn’t understand unknown.

It turned out that Edge doesn’t understand :focus-within, which is what allows the drop-down area to stay open when an input element deep within the drop-down area gets clicked. The problem was, I’d mixed supported and unsupported selectors together.

In order to make Edge work at all, I needed to repeat the whole block of “how-to-open-the-drop-down-list” styles separately for the selectors that use :focus-within, so that those selectors don’t stop the other selectors from working.

Then, as a workaround for the lack of :focus-within, I decided to attempt to detect Edge and automatically keep any .dropdown list open when the mouse is :hovering in that case. That way, it is still possible to use a focused element (such as an a href or an input) inside the drop-down area, although it will disappear early if the mouse moves off it.

The code for all this is as follows:

/* List of situations in which to show the dropdown list. */ .combobox > .downarrow:focus ~ *:last-child, .dropdown:focus > *:last-child, .dropdown > *:focus ~ *:last-child, .combobox > .sticky:last-child:hover, .dropdown > .sticky:last-child:hover, .combobox > .less-sticky:last-child:hover, .dropdown > .less-sticky:last-child:hover, .combobox > *:last-child:focus:not(.less-sticky), .dropdown > *:last-child:focus:not(.less-sticky) { display: block; opacity: 1; transition: 0.15s; pointer-events: auto; } /* focus-within not supported by Edge/IE. Unsupported selectors cause the entire block to be ignored, so we must repeat all styles for focus-within separately. */ .combobox > *:last-child:focus-within:not(.less-sticky), .dropdown > *:last-child:focus-within:not(.less-sticky) { display: block; opacity: 1; transition: 0.15s; pointer-events: auto; } /* detect Edge/IE and behave if though less-sticky is on for all dropdowns (otherwise links won't be clickable) */ @supports (-ms-ime-align:auto) { .dropdown > *:last-child:hover { display: block; opacity: 1; pointer-events: auto; } } /* detect IE and do the same thing. */ @media all and (-ms-high-contrast: none), (-ms-high-contrast: active) { .dropdown > *:last-child:hover { display: block; opacity: 1; pointer-events: auto; } }

Third, the outline style wasn’t working in Edge. Once again the problem was that Edge doesn’t support non-opaque outlines.

The solution is a special opaque style for Edge:

outline: 2px solid #8AF; /* Edge/IE can't do outline transparency */ outline: 2px solid #48F8;

Fourth, I had placed two combo boxes within a element, and attempting to open the second one always opens the first one instead. It turns out that in Edge, if you are using a mouse, you can only select the first input element within a label.

Fifth, the dropdown boxes didn’t have shadows. Once again this was because I used a non-opaque shadow, and once again Edge needed its own special CSS:

box-shadow: 1px 2px 4px 1px #666; /* Edge can't do shadow opacity */ box-shadow: 1px 2px 4px 1px #4448;

Internet Explorer 11 has almost exactly the same limitations, so fixing Edge mostly fixed IE, except that a different browser detection technique was needed for IE than Edge.

Synchronizing the popup with the top area

Unfortunately, CSS can’t do this for us. So in the final demo, JavaScript is used to update the top part of the combo box when the popup part changes. For instance, I used this jQuery-based code to update the top part of the color picker:

function parentComboBox(el) { for (el = el.parentNode; el && Array.prototype.indexOf.call(el.classList, "combobox") <= -1;) el = el.parentNode; return el; } $(".combobox .color").mousedown(function() { var c = this.style.backgroundColor; $(parentComboBox(this)).find(".color")[0]. style.backgroundColor = c; });

Final version

Click here to view the demo with source code on CodePen.