Is there a way to select the first/last non-hidden element using CSS3?

Viewed 89

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>

4 Answers

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>

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>

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>

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
Related