Most Common Way of Writing a HTML Table with Vertical Headers

Most common way of writing a HTML table with vertical headers?

First, your second option isn't quite valid HTML in the sense that all of the rows (TR) in a table should contain an equal number of columns (TD). Your header has 1 while the body has 3. You should use the colspan attribute to fix that.

Reference: "The THEAD, TFOOT, and TBODY sections must contain the same number of columns." - Last paragraph of section 11.2.3.

With that being said, the first option is the better approach in my opinion because it's readable regardless of whether or not I have CSS enabled. Some browsers (or search engine crawlers) don't do CSS and as such, it'll make your data make no sense as the header will then represent columns instead of rows.

How can I make a HTML table with headers in one vertical column?

The HTML is pretty straightforward, just be sure to use the [scope] attribute to specify the correct orientation of the table.

<table>
<tbody>
<tr>
<th scope="row">City</th>
<td>$city</td>
</tr>
<tr>
<th scope="row">Latitude</th>
<td>$latitude</td>
</tr>
<tr>
<th scope="row">Longitude</th>
<td>$longitude</td>
</tr>
<tr>
<th scope="row">Country</th>
<td>$country</td>
</tr>
</tbody>
</table>

From the docs for the [scope] attribute:

The row state means the header cell applies to some of the subsequent cells in the same row(s).

How to display vertical text in table headers with auto height / without text overflow?

If you use a pseudo element and vertical-padding, you may basicly draw a square box or <td> :
http://jsfiddle.net/qjzwG/319/

.verticalTableHeader {
text-align:center;
white-space:nowrap;
transform-origin:50% 50%;
transform: rotate(90deg);

}
.verticalTableHeader:before {
content:'';
padding-top:110%;/* takes width as reference, + 10% for faking some extra padding */
display:inline-block;
vertical-align:middle;
}

If you want to keep <td> ith a small width, table-layout:fixed + width might help.
http://jsfiddle.net/qjzwG/320/

.verticalTableHeader {
text-align:center;
white-space:nowrap;
transform: rotate(90deg);

}
.verticalTableHeader p {
margin:0 -100% ;
display:inline-block;
}
.verticalTableHeader p:before{
content:'';
width:0;
padding-top:110%;/* takes width as reference, + 10% for faking some extra padding */
display:inline-block;
vertical-align:middle;
}
table {
text-align:center;
table-layout : fixed;
width:150px
}

If you want table to still be able to grow from it's content but not from width of <th> , using a wrapper with a hudge negative margin opposite to dir/direction of document might do : apparently, the closest to your needs, http://jsfiddle.net/qjzwG/320/

<table border="1">
<tr>
<th class="verticalTableHeader"><p>First</p></th>
<th class="verticalTableHeader"><p>Second-long-header</p></th>
<th class="verticalTableHeader"><p>Third</p></th>
</tr>
.verticalTableHeader {
text-align:center;
white-space:nowrap;
transform: rotate(90deg);
}
.verticalTableHeader p {
margin:0 -999px;/* virtually reduce space needed on width to very little */
display:inline-block;
}
.verticalTableHeader p:before {
content:'';
width:0;
padding-top:110%;
/* takes width as reference, + 10% for faking some extra padding */
display:inline-block;
vertical-align:middle;
}
table {
text-align:center;
}

HTML from demo and base :

<table border="1">
<tr>
<th class="verticalTableHeader">First</th>
<th class="verticalTableHeader">Second</th>
<th class="verticalTableHeader">Third</th>
</tr>
<tr>
<td>foo</td>
<td>foo</td>
<td>foo</td>
</tr>
<tr>
<td>foo</td>
<td>foo</td>
<td>foo</td>
</tr>
<tr>
<td>foo</td>
<td>foo</td>
<td>foo</td>
</tr>
</table>

For older IE , you need to use writing-mode (CSS) :http://msdn.microsoft.com/en-us/library/ie/ms531187%28v=vs.85%29.aspx

Is a table with top and left headers possible using thead and tbody?

If you wish to use thead and tbody elements, then the former should contain the header rows and the latter all the other rows:

<table>
<thead>
<tr><th></th><th>Cow</th><th>Horse</th></tr>
</thead>
<tbody>
<tr><th>Big</th><td>1</td><td>3</td></tr>
<tr><th>Small</th><td>5</td><td>7</td></tr>
</tbody>
</table>

Colgroup for complex table with three levels of headers

This isn't as straightforward as I thought it would be, I tried numerous techniques such as just typical colgroup and then I even tried the headers attribute, which allows you to define headers via an ID.

After a bit of searching I came across this example page where someone had tried 3 level column headers (example 3) and they experienced similar issues.

After a bit of hacking about I did manage to get something that is workable and seems OK but it would need a lot more testing in other screen readers as I only have NVDA and JAWS on my PC (and don't have time to test all the browser-screen reader combinations at the moment).

A possible solution

As with anything if I can't get semantic elements to work and can't get WAI-ARIA to work then I fall back to our old friends aria-hidden and visually hidden text.

What I do is completely hide the first two header rows in the table from screen readers and then use visually hidden text to add the information back into the 3rd level headers.

So for example for column 10 I add "H1-C, H2-E" before "H3-K".

<th scole="col"><span class="visually-hidden">H1-C, H2-E,</span>H3-K</th>

This way a screen reader user is effectively presented with a normal 10 column table which is easy to navigate, but all the relevant header information is read out.

For example the first cell is read as

row 2 H1-A, H2-A,H3-A column 1 data 1a

You would obviously want to build some solution (preferably on the server but with JS would be acceptable in a pinch) that does this automatically to avoid any mistakes being made.

It is not an ideal solution, but it is the best I can come up with and is what I would do as at least all the relevant information is presented to screen reader users.

Caveat: I would only use the solution if I could automate the process, if I could not automate generation of the visually hidden text for each column I would not do anything and leave the table as it is, due to the fact that a mistake here would likely be less accessible than just leaving the table alone.

Example

table{
width: 100%;
}
th{
text-align: center;
}
th, td{
border: 1px solid #333;
}

.visually-hidden {
border: 0;
padding: 0;
margin: 0;
position: absolute !important;
height: 1px;
width: 1px;
overflow: hidden;
clip: rect(1px 1px 1px 1px); /* IE6, IE7 - a 0 height clip, off to the bottom right of the visible 1px box */
clip: rect(1px, 1px, 1px, 1px); /*maybe deprecated but we need to support legacy browsers */
clip-path: inset(50%); /*modern browsers, clip-path works inwards from each corner*/
white-space: nowrap; /* added line to stop words getting smushed together (as they go onto seperate lines and some screen readers do not understand line feeds as a space */
}
<table>
<thead>
<tr aria-hidden="true">
<th colspan="4">H1-A</th>
<th colspan="3">H1-B</th>
<th colspan="3">H1-C</th>
</tr>
<tr aria-hidden="true">
<th>H2-A</th>
<th colspan="2">H2-B</th>
<th>H2-C</th>
<th colspan="3">H2-D</th>
<th colspan="3">H2-E</th>
</tr>
<tr>
<th scole="col" height="10"><span class="visually-hidden">H1-A, H2-A,</span>H3-A</th>
<th scole="col"><span class="visually-hidden">H1-A, H2-B,</span>H3-B</th>
<th scole="col"><span class="visually-hidden">H1-A, H2-B,</span>H3-C</th>
<th scole="col"><span class="visually-hidden">H1-A, H2-C,</span>H3-D</th>
<th scole="col"><span class="visually-hidden">H1-B, H2-D,</span>H3-E</th>
<th scole="col"><span class="visually-hidden">H1-B, H2-D,</span>H3-F</th>
<th scole="col"><span class="visually-hidden">H1-B, H2-D,</span>H3-G</th>
<th scole="col"><span class="visually-hidden">H1-C, H2-E,</span>H3-I</th>
<th scole="col"><span class="visually-hidden">H1-C, H2-E,</span>H3-J</th>
<th scole="col"><span class="visually-hidden">H1-C, H2-E,</span>H3-K</th>
</tr>
</thead>
<tbody>
<tr>
<td>data 1a</td>
<td>data 2a</td>
<td>data 3a</td>
<td>data 4a</td>
<td>data 5a</td>
<td>data 6a</td>
<td>data 7a</td>
<td>data 8a</td>
<td>data 9a</td>
<td>data 10a</td>
</tr>
<tr>
<td>data 1b</td>
<td>data 2b</td>
<td>data 3b</td>
<td>data 4b</td>
<td>data 5b</td>
<td>data 6b</td>
<td>data 7b</td>
<td>data 8b</td>
<td>data 9b</td>
<td>data 10b</td>
</tr>
</tbody>
</table>

How to markup data cell that also functions as row header in HTML5?

Just because a cell is a TH, does not mean it is not also data. You should use TH with scope="row". In HTML4, you could place a scope attribute on a TD cell, in HTML5 it is no longer acceptable to have a scope attribute an a TD cell.

<table>
<thead>
<tr>
<th scope="col">Principle</th>
<th scope="col">Corresponding Guidelines</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Never leave a child unattended</th>
<td>...</td>
</tr>
</tbody>
</table>

In general most data tables have a cell that could be considered a header cell. I have seen exceptions to that rule - in particular for automatically generated data (like for example log file data) where every element in the row is of equal importance and nothing can really be considered a header.

In these instances, you could decide to dynamically set the header based on the sorting criteria but this is difficult to get to work across assistive technologies. If you were to do that, you would want to use ARIA grid markup with aria-labelledby to point to the headers. You should probably also test that solution for usability.

How to format table with additional column and row headers including vertical alignment and reactive inputs?

See related question and solution at this post, which presents an alternative (and ultimately better) solution to this question regarding descriptive column headers for to/from transition matrices: How to merge 2 row cells in data table?

Also here is code that works for that solution:

library(DT)
library(shiny)
library(dplyr)
library(htmltools)
library(data.table)

data <-
data.frame(
ID = c(1,1,1,2,2,2,3,3,3),
Period = c(1, 2, 3, 1, 2, 3, 1, 2, 3),
Values = c(5, 10, 15, 0, 2, 4, 3, 6, 9),
State = c("X0","X1","X2","X0","X2","X0", "X2","X1","X0")
)

numTransit <- function(x, from=1, to=3){
setDT(x)
unique_state <- unique(x$State)
all_states <- setDT(expand.grid(list(from_state = unique_state, to_state = unique_state)))
dcast(x[, .(from_state = State[from],
to_state = State[to]),
by = ID]
[,.N, c("from_state", "to_state")]
[all_states,on = c("from_state", "to_state")],
to_state ~ from_state, value.var = "N"
)
}

ui <- fluidPage(
tags$head(tags$style(".datatables .display {margin-left: 0;}")),
h4(strong("Base data frame:")),
tableOutput("data"),
h4(strong("Transition table inputs:")),
numericInput("transFrom", "From period:", 1, min = 1, max = 3),
numericInput("transTo", "To period:", 2, min = 1, max = 3),
radioButtons("transposeDT",
label = "From state along:",
choiceNames = c('Columns','Rows'),
choiceValues = c('Columns','Rows'),
selected = 'Columns',
inline = TRUE
),
h4(strong("Output transition table:")),
DTOutput("resultsDT"),
)

server <- function(input, output, session) {
results <-
reactive({
results <- numTransit(data, input$transFrom, input$transTo) %>%
replace(is.na(.), 0) %>%
bind_rows(summarise_all(., ~(if(is.numeric(.)) sum(.) else "Sum")))
results <- cbind(results, Sum = rowSums(results[,-1]))
})

output$data <- renderTable(data)

output$resultsDT <- renderDT(server=FALSE, {
datatable(
#StackPost solution from anuanand added the below...
data = if(input$transposeDT=='Rows')
{results()%>%transpose(make.names = 'to_state',keep.names = 'to_state')}
else {results()},
rownames = FALSE,
filter = 'none',
container = tags$table(
class = 'display',
tags$thead(
tags$tr(
tags$th(rowspan = 2, # Add the below if-else to change to/from column headers when transposing
if(input$transposeDT=='Rows')
{sprintf('From state where initial period = %s', input$transFrom)}
else{sprintf('To state where end period = %s', input$transTo)}
, style = "border-right: solid 1px;"),
tags$th(colspan = 10, # Add the below if-else to change to/from column headers when transposing
if(input$transposeDT=='Rows')
{sprintf('To state where end period = %s', input$transTo)}
else{sprintf('From state where initial period = %s', input$transFrom)}
)
),
tags$tr(
mapply(tags$th, colnames(results())[-1], style = sprintf("border-right: solid %spx;", rep(0, ncol(results()) - 1L)), SIMPLIFY = FALSE)
)
)
),
options = list(scrollX = F
, dom = 'ft'
, lengthChange = T
, pagingType = "numbers"
, autoWidth = T
, info = FALSE
, searching = FALSE
),
class = "display"
) %>%
formatStyle(c(1), `border-right` = "solid 1px")
})

}

shinyApp(ui, server)


Related Topics



Leave a reply



Submit