0

I have a table which contains multiple tbody elements — some created by the browser, and some that I have written in myself for programmatic reasons. I want to apply styling to only the first tr within my table, which I would normally use table > tr for. However, my styling cannot assume that a tbody element does or does not exist, because I don't know that every user's browser is adding in tbody elements automatically in every case.

As you can see, an unwanted border styling is applied to the first tr within the second tbody of this table. How can I get this to be applied to only the first tr in my table without regards to the presence of a tbody tag?

Tr with unwanted styling

JSFiddle demo

CSS

.my-table {
    border-collapse: collapse;
}
    .my-table td,
    .my-table th {
        text-align: left;
        padding: 6px 8px;
        align-content: center;
        box-sizing: border-box;
    } 
    .my-table td[colspan="42"] {
      text-align: center;
      background-color: lightgray;
    }
    .my-table th {
        padding-top: 12px;
        padding-bottom: 12px;
        background-color: #0e3e64;
        color: white;
        text-align: center;
    }
    .my-table tr:not(:first-child) {
        border: 1px solid #efefef;
    }

    /*here's the styling I don't want applied to that third tr in my table*/
    .my-table tr:first-child {
        border: 1px solid #0e3e64;
    }

HTML

<table class="my-table">
  <tr>
    <th>Column 1</th>
    <th>Column 2</th>
    <th>Column 3</th>
  </tr>
  <tr>
    <td colspan="42">Section 1</td>
  </tr>
  <tr>
    <td>Cell 1</td>
    <td>Cell 2</td>
    <td>Cell 3</td>
  </tr>
  <tbody>
    <tr>
      <td colspan="42">Section 2</td>
    </tr>
    <tr>
      <td>Cell 1</td>
      <td>Cell 2</td>
      <td>Cell 3</td>
    </tr>      
  </tbody>
</table>
Glorfindel
  • 21,988
  • 13
  • 81
  • 109
Jacob Stamm
  • 1,660
  • 1
  • 29
  • 53
  • 2
    I realize you've asked for a solution completely independent of `tbody`, but will you *always* have at least one `tbody`? If so, you could use `.my-table tbody:first-child tr:first-child { ... }`. Otherwise, if you truly need it to be completely independent of `tbody` as you've asked for, you'll probably be diving into a JavaScript solution - no such thing exists in CSS. – Tyler Roper Jan 09 '17 at 20:36
  • Using `tbody:first-child` occurred to me, but I'd only feel comfortable using that if I knew `tbody` was always inserted by the browser. Honestly, if I don't find a perfect solution, I will probably use this, and it will probably be fine. I'm just trying to be extra careful :) Thanks for the reply – Jacob Stamm Jan 09 '17 at 20:40
  • Understood. I can write you a jQuery function if that would make you more comfortable, because as I mentioned, unfortunately "first occurrence of an element on the entire page" is not available in CSS. – Tyler Roper Jan 09 '17 at 20:41
  • Well it wouldn't be "first occurrence of an element on the entire page", it would be "first occurrence of an element within this specified element". If that's truly not available in CSS, then I think JavaScript would have to be a fallback, as you're saying. Going off of the last comment, it's occurring to me that using `tbody:first-child` would give me problems with the presence of a preceding `thead`, which leads me to another realization: I think I'll just manually surround my header tr with `thead` and make my styling reflect that. Still requires manual entry, but it feels better to me – Jacob Stamm Jan 09 '17 at 20:45
  • 1
    I've posted an answer that makes use of jQuery if you'd be more comfortable using that instead. – Tyler Roper Jan 09 '17 at 20:45
  • All browsers will add tbody. Which ones do you think will not? They have to in order to make conformant HTML. –  Jan 09 '17 at 20:50
  • @torazaburo I'm not quite willing to go on that assumption, even if 99.9% do add it, because sometimes tables are manually appended to the DOM via JavaScript, and who knows if the `tbody` element is still automatically added in those cases. Regardless, even if it is *always* added in every single case, using a `tbody:first-child` kind of styling would be problematic in the event that I have a `thead` followed by a `tbody`. – Jacob Stamm Jan 09 '17 at 21:02
  • `` will not displace a `` if `first-of-type` is used – zer00ne Jan 09 '17 at 21:11
  • @zer00ne Yes, but if a `thead` is present, then I don't want any special styling to happen to the first `tbody`. Essentially, I was using `tbody:first-child` as the header section, when I should have just been using `thead` – Jacob Stamm Jan 09 '17 at 21:51
  • You can use `tbody:first-of-type`. –  Jan 09 '17 at 22:05
  • @JacobStamm I have just updated my answer, I think this time I have what you're asking. – MarioZ Jan 09 '17 at 22:07

6 Answers6

2

Unfortunately in your case, CSS does not provide a "first occurrence on page", nor a "first occurrence within parent" selector that ignores immediate-parent. As CSS stands for Cascading Style Sheets, it doesn't exactly fit within the inherent functionality of the language - default behavior is directly related to parent elements, rather than an entire page.

If your table was always going to have a tbody, you could do the following:

.my-table tbody:first-of-type tr:first-child { ... }

Though, if you truly wanted to select the first occurrence of a <tr> within .my-table, you'd need a JavaScript solution. For this, I'd suggest jQuery, simply because it makes DOM/element manipulation much simpler:

$(".my-table tr").first().css("border", "1px solid #0e3e64");

EDIT: As torazaburo mentions in the comments, the second portion of this answer isn't exactly necessary. Regardless of whether or not a tbody is declared, the browser will automatically insert one into all <table> elements. For this reason, selecting "first row of first tbody" should be a sufficient solution in all cases.

Community
  • 1
  • 1
Tyler Roper
  • 21,445
  • 6
  • 33
  • 56
  • The second part of your answer assumes that the tbody might be absent. Why do you think that? In what cases will it be absent? –  Jan 09 '17 at 20:53
  • @Syden It works fine. The border color is the same as the background-color. Change it to red and it works. @torazaburo I suppose this is true, though I've changed my first to be `tbody:first-of-type`, realizing that `first-child` would get thrown off at the occurrence of a `thead`. **EDIT:** Syden, while the code is correct in theory, you were right that it was not working - though it was because I had a trailing semi-colon in the second parameter of my `.css()`. – Tyler Roper Jan 09 '17 at 20:56
  • 1
    @Syden See my edit above - theoretically it works, I just had a typo. There should be no `;` at the end of the css declaration. Thanks for the heads-up. – Tyler Roper Jan 09 '17 at 20:59
  • @Santi could you take a look at my answer? – MarioZ Jan 09 '17 at 22:15
1

A good method is to use additional classes:

css:

/* Custom CSS class */
tr .custom {
color: #ff0000;
}

html:

<tr class="custom"><th>Column 1</th></tr>

Note, you can add this to existing class as <tr class="otherCls custom">

LowMatic
  • 223
  • 2
  • 13
  • This could work. It does cause *a little* extra CSS to be processed on an element (styling I want > unwanted styling > the styling I want *again*), but functionally speaking, it would work fine. – Jacob Stamm Jan 09 '17 at 20:59
  • Yes, basically this method is legit and cover the problem solving for this case. due, other classes my also use `position: absolute` which may conflict and mostly will be solver by tuning the `z-index` right... – LowMatic Jan 09 '17 at 21:11
0

You can assign a specific "id" to your third tr and use it in the css.

<style>
.my-table {
    border-collapse: collapse;
}
    .my-table td,
    .my-table th {
        text-align: left;
        padding: 6px 8px;
        align-content: center;
        box-sizing: border-box;
    } 
    .my-table td[colspan="42"] {
      text-align: center;
      background-color: lightgray;
    }
    .my-table th {
        padding-top: 12px;
        padding-bottom: 12px;
        background-color: #0e3e64;
        color: white;
        text-align: center;
    }
    .my-table tr:not(:first-child) {
        border: 1px solid #efefef;
    }

    /*here's the styling I don't want applied to that third tr in my table*/
    #third{
        border: 5px solid #0e3e64;
    }
</style>
</head>
<body>
<table class="my-table">
  <tr>
    <th>Column 1</th>
    <th>Column 2</th>
    <th>Column 3</th>
  </tr>
  <tr>
    <td colspan="42">Section 1</td>
  </tr>
  <tr>
    <td>Cell 1</td>
    <td>Cell 2</td>
    <td>Cell 3</td>
  </tr>
  <tbody>
    <tr>
      <td colspan="42">Section 2</td>
    </tr>
    <tr id="third">
      <td>Cell 1</td>
      <td>Cell 2</td>
      <td>Cell 3</td>
    </tr>      
  </tbody>
</table>
</body>
</html>
TECHMIX.be
  • 11
  • 2
0

As @Santi stated in his answer, what I'm asking for isn't really possible with raw CSS, because table tr:first-child doesn't translate as "first tr within table", it translates more like "first tr within its parent element within table", which may be the table itself, or it may be tbody or thead.

His answer works perfectly fine, but for those reading this who don't want to use JavaScript, I'll share what solution I'm going to use.

I'm just going to explicitly surround the header row in each of my tables with a thead wrapper, and change my problematic styling code to look like this:

.my-table > thead > tr:first-child {
    border: 1px solid #0e3e64;
}

It still requires me to manually change old tables, but it doesn't require me to worry about multiple occurrences of tbody elements.

Jacob Stamm
  • 1,660
  • 1
  • 29
  • 53
0

According to this post, any browser worth their salt will insert a <tbody> into a <table>, and according to this:

8.2.5.4.9 The "in table" insertion mode ...

A start tag whose tag name is one of: "td", "th", "tr"

Act as if a start tag token with the tag name "tbody" had been seen, then reprocess the current token.

Coincides with that default behavior. Can you show us any example of which this doesn't happen? So using:

.my-table tbody:first-of-type tr:first-of-type { ... }

or

.my-table tbody:first-of-type tr:first-child { ... }

wouldn't work?

This Snippet shows a basic <table> with <tr> please review it in different browsers and the result in console should report the same:

SNIPPET

var row = document.querySelector('tr');
console.log(row.parentNode.nodeName);
<table>
  <tr></tr>
</table>
Community
  • 1
  • 1
zer00ne
  • 41,936
  • 6
  • 41
  • 68
-1

Ok, here you are. Style only applied to the first tr child no matter if you have <thead>, <tbody>, both or none:

table {
 width: 200px;
 height: 400px;
}
thead {
 height: 100px;
}
tr {
 background-color: grey;
 height: 100px;
}
table > :nth-child(1) > tr:nth-of-type(1) {
 background-color: red;
}
<body>
Head and body
<table>
<thead>
 <tr><td>Head</td></tr>
</thead>
<tbody>
 <tr><td>Body</td></tr>
 <tr><td></td></tr>
 <tr><td></td></tr>
</tbody>
</table>
No head
<table>
 <tr><td></td></tr>
 <tr><td></td></tr>
 <tr><td></td></tr>
 <tr><td></td></tr>
</table>
</body>
MarioZ
  • 981
  • 7
  • 16
  • I'm afraid not. Even using `:nth-of-type(1)` instead of `:first-child`, it's still going to be the first occurrence *within its parent*, which is likely to be `tbody`, not `table` – Jacob Stamm Jan 09 '17 at 21:05
  • @JacobStamm you can see, I updated, that it works for the child of any table element if its stated this way. – MarioZ Jan 09 '17 at 21:09
  • I appreciate you taking the time to answer this. However, even though you didn't provide a `tbody` or `thead` in that second table, if you inspect the source of your page, you'll see that the browser actually inserted a `tbody` element in there automatically. So, the CSS doesn't treat the body of the first table and the second table any differently. The question remains, what happens if/when a browser (probably an older one) *doesn't* automatically add in `tbody`? https://s25.postimg.org/kxwx1kj1b/2017_01_09_17_29_07.png – Jacob Stamm Jan 09 '17 at 22:32
  • 1
    @JacobStamm The answer is edge cases such as ancient browsers...(Netscape...Mosaic...IE3.0?) are not relevant. What browser (including nightly builds) does not conform to this behavior? – zer00ne Jan 09 '17 at 22:38
  • The `` tag is added because everything inside a table that is not `` or `` is ``. This quote is from w3c HTML4.01 documentation (1.999): "The TBODY start tag is always required except when the table contains only one table body and no table head or foot sections. The TBODY end tag may always be safely omitted." tbodies are quite old, and you can check that cross-browser cross-version supported in w3c site. For older browsers child selectors are the problem since they are css3. @JacobStamm I'm using the tools we have now. – MarioZ Jan 09 '17 at 23:02