2

I want to style form fieldsets with a margin, but remove margin-top for the first non-hidden fieldset and margin-bottom for the last non-hidden fieldset (any fieldset may be set hidden by the script dynamically). I tried the following CSS with no luck (fs2 is expected to have no margin-top but actually has; fs3 is expected to have no margin-bottom but actually has). Is there a way to do something like this using CSS3?

form > fieldset {
  margin: .5em;
}

/* this doesn't work */
form > fieldset:not([hidden]):first-of-type {
  margin-top: 0;
}

/* this doesn't work */
form > fieldset:not([hidden]):last-of-type {
  margin-bottom: 0;
}
<form>
  <fieldset hidden>fs1</fieldset>
  <fieldset>fs2</fieldset>
  <fieldset>fs3</fieldset>
  <fieldset hidden>fs4</fieldset>
</form>
Danny Lin
  • 2,050
  • 1
  • 20
  • 34
  • Do you intend for border collapse to be enabled? – thgaskell Apr 15 '21 at 18:04
  • 1
    You mean margin collapse? I did have it in mind but want a general solution of such case without having to count on margin collapse. – Danny Lin Apr 15 '21 at 18:12
  • 2
    There isn't a selector-based solution for the last such element. A robust solution for the first such element involves multiple selectors and multiple CSS rules, and isn't even completely foolproof without taking advantage of margin collapsing. This is supposed to be addressed with fieldset:nth-child(1 of :not([hidden])) and fieldset:nth-last-child(1 of :not([hidden])) which still isn't widely supported. – BoltClock Apr 15 '21 at 18:17
  • 1
    See also https://stackoverflow.com/questions/5545649/can-i-combine-nth-child-or-nth-of-type-with-an-arbitrary-selector – BoltClock Apr 15 '21 at 18:19
  • The :last-of-type pseudo-class looks for the last element of its type *and also* for that element to be not hidden based on your selector. Your last `fieldset` element is hidden, so nothing will apply. Selectors are all-or-nothing, not any-of-the-above. – TylerH Apr 15 '21 at 18:19
  • What is it that you want to accomplish? Why only the first visible element? What if there are multiple non-contiguous visible elements in this list? – TylerH Apr 15 '21 at 18:22
  • @TylerH: It's not uncommon to want to remove the topmost and bottommost margins from a series of elements, and it's not uncommon to accomplish that by targeting the first and last visible elements. It's in a similar vein to applying rounded corners to the first and last visible elements in a series. – BoltClock Apr 15 '21 at 18:24
  • @BoltClock I guess I was thinking there would be more items after the ones selected, but that doesn't make sense after shaking my head a bit and thinking about it a second time. It seems like it would be better to just remove unwanted elements rather than applying an attribute to them. That way `:first-of-type` and `:last-of-type` would work. – TylerH Apr 15 '21 at 18:33
  • @TylerH Hiding doesn't always mean unwanted. Elements hidden by the script may be unhidden sometimes, and that's why removing is not necessarily better than hiding. – Danny Lin Apr 17 '21 at 09:01
  • @DannyLin You can insert elements into an array while they're not needed, since you're already using JS. Like airplanes in a holding pattern at an airport. – TylerH Apr 19 '21 at 14:10

4 Answers4

2

gap was created for such use case

form {
  display:grid;
  gap:0.5em;
  border:1px solid red;
  margin:10px;
}
fieldset {
  margin:0;
}
<form>
  <fieldset hidden>fs1</fieldset>
  <fieldset>fs2</fieldset>
  <fieldset>fs3</fieldset>
  <fieldset hidden>fs4</fieldset>
</form>


<form>
  <fieldset hidden>fs1</fieldset>
  <fieldset>fs2</fieldset>
  <fieldset>fs3</fieldset>
  <fieldset >fs4</fieldset>
</form>

<form>
  <fieldset hidden>fs1</fieldset>
  <fieldset hidden>fs2</fieldset>
  <fieldset>fs3</fieldset>
  <fieldset >fs4</fieldset>
</form>

<form>
  <fieldset hidden>fs1</fieldset>
  <fieldset hidden>fs2</fieldset>
  <fieldset>fs3</fieldset>
  <fieldset hidden>fs4</fieldset>
</form>
Temani Afif
  • 245,468
  • 26
  • 309
  • 415
  • Thanks. Unfortunately browser support is awful. – Danny Lin Apr 17 '21 at 09:02
  • @DannyLin euh, are you sure? ... Even IE support CSS grid: https://caniuse.com/css-grid ... CSS grid is not something new, it has been around since too long now – Temani Afif Apr 17 '21 at 10:09
  • @TermaniAfif Support for grid is difinitely not support for gap. According to [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/gap), IE does not support the `gap` property. – Danny Lin Apr 17 '21 at 11:36
  • 1
    @DannyLin and this is what you call *awful*? IE not supporting something? ... IE (the dead browser) doesn't support *anything* useful. – Temani Afif Apr 17 '21 at 11:42
  • @TermaniAfif Though it may be context dependent, my apps usually target Fx >= 52 and Chromium >= 55 (the first version with JS async/await), and both do not support it. This is certainly newer than many other techniques I am using. – Danny Lin Apr 17 '21 at 11:53
  • 1
    @DannyLin use the old syntax `grid-row-gap` – Temani Afif Apr 17 '21 at 11:55
1

You can use the adjacent sibling selector, + to do this with:

form > fieldset[hidden] + fieldset {
  margin-top: 0;
}

This will set the first sibling of the hidden fieldset to have no top margin:

form > fieldset {
  margin: .5em;
}

/* this doesn't work */
form > fieldset[hidden] + fieldset {
  margin-top: 0;
}
<form>
  <fieldset hidden>fs1</fieldset>
  <fieldset>fs2</fieldset>
  <fieldset>fs3</fieldset>
</form>
j08691
  • 204,283
  • 31
  • 260
  • 272
  • 1
    This will be buggy if I have hidden fs1, non-hidden fs2, hidden fs3, and non-hidden fs4. In such case fs4 will be unexpectedly have margin-top removed. – Danny Lin Apr 15 '21 at 17:42
0

Depending on how the rest of your page styles are written, a common technique is to just apply margin to the bottom of your elements.

/* Remove top margin */
margin: 0 0.5em 0.5em;

But since it looks like you want to remove all vertical margin it may be easier to apply a negative margin to the top and bottom of the form?

form > fieldset {
  margin: .5em;
}
form {
  margin: -0.5em 0;
}
<span>Lorem</span>
<form>
  <fieldset hidden>fs1</fieldset>
  <fieldset>fs2</fieldset>
  <fieldset>fs3</fieldset>
  <fieldset hidden>fs4</fieldset>
</form>
<span>Ipsum</span>
thgaskell
  • 12,772
  • 5
  • 32
  • 38
-3

As @j08691 answered, the adjacent sibling selector (+), maybe do the trick. You can also use with the first-of-type selector to affect only the first occurence.

form > fieldset[hidden]:first-of-type + fieldset
  • 1
    This will be buggy if I have hidden fs1, hidden fs2, and non-hidden fs3. In such case fs3 will be unexpectedly have margin-top preserved. – Danny Lin Apr 15 '21 at 17:57
  • 2
    :first-of-type will always choose the first fieldset. See https://stackoverflow.com/questions/2717480/css-selector-for-first-element-with-class – BoltClock Apr 15 '21 at 18:07