0

I am developing a configuration page using React. I have a list of configurable elements that are displayed in a table, and the "save" button is disabled and made transparent until a data field is modified, at which point the button is enabled, and becomes fully opaque.

However, when the button is in the disabled state, the buttons are rendered over top of parent elements, such as the sticky table header and nav bar. This becomes very apparent when scrolling. Enabling the buttons fixes the issue, and are no longer rendered over the parent elements.

Currently I am using the react-bootstrap Button component to render the button, but using a regular HTML button produces the same results. It also seems both disabling the button, or changing the opacity field independently will cause the issue as well.

<td className="saveButton">  
  <Button
    variant="primary"
    id={this.props.id}
    disabled={!this.state.modified} //Only enable saving after data has been modified
    style={this.state.modified ? {opacity: 1} : {opacity: 0.3}}
    onClick={this.updateListItem}
  >Save</Button>
</td>

The buttons should disappear beneath the sticky table header and navbar with the rest of the table row, but the buttons that are in a disabled state end up being rendered over top, like so:

transparent buttons being rendered incorrectly

(Note the enabled button correctly scrolling beneath the table header)

Any ideas where I've gone wrong?

  • Is the "sticky table header" where the `Save All` and `Add` buttons are? Try adding a `z-index: 1` or greater to the header `hr`. – sallf Oct 03 '19 at 19:05
  • Yes, that is the table header, that scrolls down with the viewport. Adding a z-index to the `` or `` elements does not make a difference. This bug seems to occur with any opacity other than 1 – Branden Rice Oct 03 '19 at 19:43

1 Answers1

2

I was able to reproduce your bug with a basic table layout using a sticky header. If you un-comment the z-index in the th you'll see it fixes the problem. https://codepen.io/sallf/pen/BaaBwmR

You've only shown the button inside the table, so I don't know exactly how you have your table set up, but this seems like a likely fix.

th {
  position: sticky;
  top: 0;
  background: white;
  // Remove z-index and td will appear
  // to be over the td
  z-index: 1;
}

Sticky header bug with no z-index

Sticky header with z-index

As to why this is happening, I really don't know. It seems like a bug in the browser to me, but would be curious if anyone knows more.

Update

This happens because any element with an opacity less than 1 creates a new stacking context. Position inside a stacking context is determined by a stacking order.

According to the color module:

If an element with opacity less than 1 is not positioned, then it is painted on the same layer, within its parent stacking context, as positioned elements with stack level 0.

This basically means that a static element with opacity < 1 will be on the same stack level as a positioned element with the default z-index: auto (when they share the same parent stacking context).

When elements are on the same stack level, the stacking order is determined by the rendering tree (aka order of elements in your html). In this case, since the td comes after th in the html tree, td will be painted in front of th.

See an example on What No One Told You About Z-Index.

Related question.

sallf
  • 2,583
  • 3
  • 19
  • 24
  • Thank you, setting the z-index did solve the issue! I was applying the inline style incorrectly as React needs it to be converted to camel case i.e. `style={{ zIndex: 1 }}` As to why this only happens when the opacity is changed is odd, and I would be interested to know the reason behind it as well. – Branden Rice Oct 04 '19 at 14:41
  • @BrandenRice I found additional information as to why opacity affects the stacking order. See my update if you're interested. – sallf Jan 17 '20 at 17:07