Table Elements
FlightPlan offers two ways to build tables. Use Table for hand-built tables where you control every cell. Use SimpleTable when you have a DataTable from the database and want it rendered with optional column formatters.
Choosing a Table Style
Neither Table nor SimpleTable applies any styling by default. You always pass a CSS class to control the look. There are three common choices, in order of effort:
Choice 1: No class — unstyled (probably not what you want)
If you construct a table with no class, the browser renders a bare HTML table — no borders, no padding, no header background. Cells are just text with whitespace separators. Useful for very rare cases where you'll style it externally; almost never the right choice.
// No class — produces an invisible table
content.Add(new SimpleTable(data)
.DefineColumn("employee_name", "Name")
.DefineColumn("role", "Role"));
| Name | Role |
|---|---|
| Ada Lovelace | Engineer |
| Grace Hopper | Architect |
Choice 2: CoreCss.BorderTable — minimal Excel-style grid
The framework's built-in default for clean, functional tables. Defined in CoreStyles, so it works in any app without adding stylesheet code. Gray header, thin borders, hover highlight on rows. Use this when you just want a readable data grid and don't care about visual flourish.
using CoreCss = FlightPlan.Core.Dom.Styles.CoreStyles.Css;
content.Add(new SimpleTable(data, CoreCss.BorderTable)
.DefineColumn("employee_name", "Name")
.DefineColumn("role", "Role"));
| Name | Role |
|---|---|
| Ada Lovelace | Engineer |
| Grace Hopper | Architect |
Choice 3: Your own suite or app CSS class — full control
When you want a particular look (colored headers, alternating rows, condensed padding), define a class in your app's StyleLibrary and pass its name. StateLawTable uses "data-table"; SCM uses its own. Whatever you define, just pass the string.
// In YourAppStyles.cs:
lib.DefineSelector(".my-table th")
.Property("background", "#2c3e50")
.Property("color", "white")
.Property("padding", "12px");
lib.DefineSelector(".my-table td")
.Property("padding", "10px")
.Property("border-bottom", "1px solid #eee");
// In a page:
content.Add(new SimpleTable(data, "my-table")
.DefineColumn("employee_name", "Name"));
Quick Tables: Table.AddHeaderRow / AddRow
The fastest path for hand-built tables. AddHeaderRow takes column titles; AddRow takes cell values as objects (auto-converted to strings). Internally these create the Thead, Tbody, Tr, Th, and Td for you. Pass CoreCss.BorderTable (or your own class) to get visible styling.
var table = content.Add(new Table(CoreCss.BorderTable));
table.AddHeaderRow("Name", "Role", "Hire Date");
table.AddRow("Ada Lovelace", "Engineer", "1843-12-10");
table.AddRow("Grace Hopper", "Architect", "1944-06-01");
table.AddRow("Alan Turing", "Researcher", "1936-05-28");
| Name | Role | Hire Date |
|---|---|---|
| Ada Lovelace | Engineer | 1843-12-10 |
| Grace Hopper | Architect | 1944-06-01 |
| Alan Turing | Researcher | 1936-05-28 |
Manual Construction: Thead, Tbody, Tr, Th, Td
Use the explicit form when you need control — custom CSS on individual rows or cells, child elements inside cells, or anything beyond plain text.
var table = content.Add(new Table(CoreCss.BorderTable));
var thead = table.Add(new Thead());
var headRow = thead.Add(new Tr());
headRow.Add(new Th("Product"));
headRow.Add(new Th("Price"));
var tbody = table.Add(new Tbody());
var row1 = tbody.Add(new Tr());
row1.Add(new Td("Widget"));
row1.Add(new Td("$12.50"));
var row2 = tbody.Add(new Tr());
row2.Add(new Td("Gadget"));
row2.Add(new Td("$45.00"));
| Product | Price |
|---|---|
| Widget | $12.50 |
| Gadget | $45.00 |
Cell Spanning: WithColSpan
Both Th and Td support WithColSpan(n) to span multiple columns. Useful for section headers inside the table body.
var table = content.Add(new Table(CoreCss.BorderTable));
table.AddHeaderRow("Item", "Q1", "Q2", "Q3", "Q4");
var sectionRow = table.AddEmptyRow();
sectionRow.Add(new Td("— 2025 Sales —").WithColSpan(5));
table.AddRow("Widget", "100", "120", "95", "140");
table.AddRow("Gadget", "80", "90", "110", "130");
| Item | Q1 | Q2 | Q3 | Q4 |
|---|---|---|---|---|
| — 2025 Sales — | ||||
| Widget | 100 | 120 | 95 | 140 |
| Gadget | 80 | 90 | 110 | 130 |
Cells with Child Elements
A Td with no constructor text but with child elements added renders the children inside the cell. The render priority is: children > raw HTML > text. Useful for cells that contain links, buttons, or formatted markup.
var table = content.Add(new Table(CoreCss.BorderTable));
table.AddHeaderRow("User", "Action");
var row = table.AddEmptyRow();
row.Add(new Td("Ada Lovelace"));
var actionCell = row.Add(new Td());
actionCell.Add(new A("/users/ada/edit", "Edit"));
actionCell.Add(new Text(" | "));
actionCell.Add(new A("/users/ada/delete", "Delete"));
SimpleTable: Auto-Generated Columns
The fastest possible data-bound table. Pass a DataTable (or any DbResults that exposes one) and nothing else — SimpleTable auto-generates one column per DataColumn, using the column name as both the field reference and the header. Pair with CoreCss.BorderTable (or any CSS class) for visible styling, or pass no class for an unstyled table.
DataTable data = SomeAdapter.LoadData(); // returns DataTable // Zero column config — every DataColumn becomes a Th with the column name content.Add(new SimpleTable(data, CoreCss.BorderTable));
| employee_id | employee_name | role | hire_date |
|---|---|---|---|
| 101 | Ada Lovelace | Engineer | 12/10/1843 12:00:00 AM |
| 102 | Grace Hopper | Architect | 6/1/1944 12:00:00 AM |
| 103 | Alan Turing | Researcher | 5/28/1936 12:00:00 AM |
Use this when you want a quick read-only view of whatever the database returned — admin tools, debug pages, or any spot where the raw column names are good enough as headers.
SimpleTable: Named Columns and Formatters
When you want control over which columns appear, what their headers say, or how values are formatted, use DefineColumn. The column order in your DefineColumn calls determines the column order in the rendered table — fields not listed are excluded.
var simple = new SimpleTable(data, CoreCss.BorderTable)
.DefineColumn("employee_name", "Name")
.DefineColumn("role", "Role")
.DefineColumn("hire_date", "Hired",
value => Convert.ToDateTime(value).ToString("yyyy-MM-dd"))
.DefineColumn("salary", "Salary",
value => $"${Convert.ToDecimal(value):N2}");
content.Add(simple);
| Name | Role | Hired | Salary |
|---|---|---|---|
| Ada Lovelace | Engineer | 1843-12-10 | $92,500.00 |
| Grace Hopper | Architect | 1944-06-01 | $118,000.00 |
| Alan Turing | Researcher | 1936-05-28 | $105,500.00 |
Row Formatters: Multi-Field Cells
DefineColumn(header, rowFormatter) gives the formatter the entire DataRow, so you can build a cell from multiple fields. AllowHtml is set automatically — return any markup you need.
var simple = new SimpleTable(data, CoreCss.BorderTable)
.DefineColumn("Name", row => $"<strong>{row["last_name"]}</strong>, {row["first_name"]}")
.DefineColumn("salary", "Salary",
value => $"${Convert.ToDecimal(value):N2}");
content.Add(simple);
| Name | Salary |
|---|---|
| Lovelace, Ada | $92,500.00 |
| Hopper, Grace | $118,000.00 |
Pulling Formatters Into Helper Methods
Inline lambdas are fine for one-liners, but once a formatter has any real logic — a permission check, a multi-branch return, anything that takes more than one expression — extract it into a private method and pass it as a method-group reference. DefineColumn happily accepts any Func<DataRow, string>, including a method group, which reads cleaner than a multi-line lambda nested inside a fluent chain.
// Inline lambda — fine when the cell is a single expression
.DefineColumn("Schema", row =>
$"<a href=\"/Team/DD/TableList?DB={row["db_name"]}&Schema={row["schema_name"]}\">{row["schema_name"]}</a>")
// Method group — preferred when the cell needs branching, permission checks,
// or anything you'd want to read top-to-bottom rather than parse as one expression
.DefineColumn("Action", BuildEditLink)
private string BuildEditLink(DataRow row)
{
string owners = $"{row["owners"]}";
bool canEdit = owners.Contains(MudID) || _userAdmin;
if (!canEdit)
return "";
return $"<a href=\"/Team/DD/SchemaEdit?DB={row["db_name"]}&Schema={row["schema_name"]}\">Edit</a>";
}
The method captures page-level state (MudID, _userAdmin, role flags) just like a closure would, but it gets a real name, a real signature, and the option to be unit-tested in isolation. The fluent column chain stays scannable — column declarations read as a list of (header, source) pairs, with the actual rendering logic factored away. Use this pattern any time the formatter is more than ~40 characters of body, OR contains conditional logic, OR appears in more than one column.
SimpleTable: Adding Action Columns (AddColumn / InsertColumn)
Many tables need columns that aren't in the DataTable schema — Edit/Delete links, checkboxes, status badges built from multiple fields. AddColumn tacks a column onto the end of the rendered table; InsertColumn places one at a specific position (use index 0 to prepend). Both work with auto-generated columns (no DefineColumn needed) or on top of explicit DefineColumn calls, so you can mix and match.
Addolumn: An Action Column on the Right
The common case: let SimpleTable auto-generate the data columns from the DataTable, then tack an Edit link on the end. No DefineColumn calls — the schema columns appear first, in the order the DataTable provides them, and the appended column comes last.
content.Add(new SimpleTable(data, CoreCss.BorderTable)
.AddColumn("Action", row =>
$"<a href=\"/users/{row["employee_id"]}/edit\">Edit</a>"));
| employee_id | employee_name | role | Action |
|---|---|---|---|
| 101 | Ada Lovelace | Engineer | Edit |
| 102 | Grace Hopper | Architect | Edit |
| 103 | Alan Turing | Researcher | Edit |
InsertColumn: A Column at the Start (or Any Position)
InsertColumn(0, ...) puts the column at the front — useful for a checkbox or select column on a list page. Any non-negative index works; an index past the end clamps to the end. The example below combines both methods: a Select column at the front and an Action column on the right.
content.Add(new SimpleTable(data, CoreCss.BorderTable)
.InsertColumn(0, "Select", row =>
$"<input type=\"checkbox\" name=\"sel_{row["employee_id"]}\" />")
.AddColumn("Action", row =>
$"<a href=\"/users/{row["employee_id"]}/edit\">Edit</a>"));
| Select | employee_id | employee_name | role | Action |
|---|---|---|---|---|
| 101 | Ada Lovelace | Engineer | Edit | |
| 102 | Grace Hopper | Architect | Edit | |
| 103 | Alan Turing | Researcher | Edit |
Combining with DefineColumn
AddColumn and InsertColumn also layer on top of DefineColumn. Use this when you want explicit control over which data columns appear AND an extra action column — DefineColumn declares the data columns in order, then Append/Insert splice in the extras.
content.Add(new SimpleTable(data, CoreCss.BorderTable)
.DefineColumn("employee_name", "Name")
.DefineColumn("role", "Role")
.AddColumn("Action", row =>
$"<a href=\"/users/{row["employee_id"]}/edit\">Edit</a>"));
Insert Order Semantics
InsertColumn follows List<T>.Insert semantics — each call physically inserts at the given index of the current list, so later calls at the same index push earlier inserts forward. To insert multiple columns at the front in a specific left-to-right order, use sequential indexes:
// Result: [A, B, ...schema columns] .InsertColumn(0, "A", ...) .InsertColumn(1, "B", ...) // Result: [B, A, ...schema columns] — A got pushed right when B was inserted at 0 .InsertColumn(0, "A", ...) .InsertColumn(0, "B", ...)
Empty State Message
If the DataTable has zero rows, SimpleTable renders a paragraph with the empty message instead of an empty table. Customize via WithEmptyMessage.
var simple = new SimpleTable(emptyData, CoreCss.BorderTable)
.WithEmptyMessage("No employees found for the selected filters.")
.DefineColumn("employee_name", "Name");
No employees found for the selected filters.
Scroll Behavior: Flow vs Excel
Flow (default) lets the table size to its container; cells wrap their text naturally and the page handles all scrolling. Excel mode is for wide data tables: cells switch to single-line nowrap, the table sizes to its widest content, and a wrapping div provides horizontal scrolling so the page layout doesn't break when the table is wider than its container. Vertical scroll is still the page's in Excel mode — to put the vertical scrollbar on the table itself, combine with WithStickyHeader (below).
// Excel mode: wide-data scrolling with single-line cells
var simple = new SimpleTable(data, CoreCss.BorderTable)
.WithScrollBehavior(TableScrollBehavior.Excel)
.DefineColumn("employee_name", "Name")
.DefineColumn("role", "Role")
.DefineColumn("hire_date", "Hired");
Scroll horizontally inside the box. Vertical scroll is the page's — to see that, scroll the page itself.
| ID | Name | Role | Department | Location | Hired | Notes |
|---|---|---|---|---|---|---|
| 1000 | Edsger Hopper | Engineer | Infrastructure | London, UK | 2017-07-08 | Internal-tools focus: built the deploy dashboard everyone uses, no fanfare. |
| 1001 | Donald Hopper | Tech Lead | Data Engineering | Cambridge, MA | 2019-11-05 | Owns the migration from legacy auth middleware to OAuth2 — multi-quarter project. |
| 1002 | Linus Turing | Researcher | Platform | Bangalore, IN | 2020-07-13 | On-call rotation for the ingest pipeline. Handles after-hours pages calmly. |
| 1003 | Grace Lovelace | Tech Lead | Developer Tools | Mountain View, CA | 2015-06-04 | Internal-tools focus: built the deploy dashboard everyone uses, no fanfare. |
| 1004 | Grace Stroustrup | Tech Lead | Infrastructure | London, UK | 2015-05-26 | Owns the migration from legacy auth middleware to OAuth2 — multi-quarter project. |
| 1005 | Donald Hoare | Engineer | Infrastructure | Bangalore, IN | 2022-03-04 | On-call rotation for the ingest pipeline. Handles after-hours pages calmly. |
| 1006 | Grace Lovelace | Engineer | Security | Zurich, CH | 2016-02-08 | Owns the migration from legacy auth middleware to OAuth2 — multi-quarter project. |
| 1007 | Barbara Turing | Researcher | Platform | London, UK | 2020-10-16 | Drove the bundle-size reduction effort, shipped a 40% improvement to first paint. |
| 1008 | Ada Hamilton | Tech Lead | Security | Cambridge, MA | 2015-02-16 | On-call rotation for the ingest pipeline. Handles after-hours pages calmly. |
| 1009 | Alan Lovelace | Tech Lead | Platform | Mountain View, CA | 2015-02-12 | Internal-tools focus: built the deploy dashboard everyone uses, no fanfare. |
| 1010 | Margaret Knuth | Architect | Platform | Bangalore, IN | 2015-02-15 | Mentors junior team members on distributed systems patterns and interview prep. |
| 1011 | Ada Dijkstra | Engineer | Developer Tools | Bangalore, IN | 2019-06-03 | On-call rotation for the ingest pipeline. Handles after-hours pages calmly. |
| 1012 | Edsger Lovelace | Architect | Platform | Mountain View, CA | 2022-08-05 | Drove the bundle-size reduction effort, shipped a 40% improvement to first paint. |
| 1013 | Margaret Turing | Principal Engineer | Infrastructure | Zurich, CH | 2015-03-09 | Cross-functional partner with Product on the new metrics surface — strong communicator. |
| 1014 | Bjarne Torvalds | Tech Lead | Platform | London, UK | 2019-05-02 | Owns the migration from legacy auth middleware to OAuth2 — multi-quarter project. |
| 1015 | Barbara Hopper | Engineer | Data Engineering | Mountain View, CA | 2019-11-30 | Cross-functional partner with Product on the new metrics surface — strong communicator. |
| 1016 | Barbara Dijkstra | Tech Lead | Data Engineering | Mountain View, CA | 2024-06-26 | Drove the bundle-size reduction effort, shipped a 40% improvement to first paint. |
| 1017 | Ada Torvalds | Principal Engineer | Platform | Cambridge, MA | 2017-07-18 | Mentors junior team members on distributed systems patterns and interview prep. |
| 1018 | Alan Knuth | Architect | Platform | Bangalore, IN | 2016-02-29 | Mentors junior team members on distributed systems patterns and interview prep. |
| 1019 | Linus Torvalds | Tech Lead | Developer Tools | London, UK | 2015-05-28 | Cross-functional partner with Product on the new metrics surface — strong communicator. |
| 1020 | Tony Turing | Principal Engineer | Developer Tools | Cambridge, MA | 2020-03-23 | Drove the bundle-size reduction effort, shipped a 40% improvement to first paint. |
| 1021 | Edsger Hopper | Senior Engineer | Platform | London, UK | 2017-03-18 | Cross-functional partner with Product on the new metrics surface — strong communicator. |
| 1022 | Bjarne Liskov | Engineer | Developer Tools | London, UK | 2017-10-28 | Drove the bundle-size reduction effort, shipped a 40% improvement to first paint. |
| 1023 | Donald Liskov | Engineer | Infrastructure | Cambridge, MA | 2023-06-11 | On-call rotation for the ingest pipeline. Handles after-hours pages calmly. |
| 1024 | Linus Stroustrup | Architect | Infrastructure | Bangalore, IN | 2022-10-07 | Mentors junior team members on distributed systems patterns and interview prep. |
| 1025 | Grace Lovelace | Senior Engineer | Developer Tools | Zurich, CH | 2018-11-15 | Drove the bundle-size reduction effort, shipped a 40% improvement to first paint. |
| 1026 | Margaret Hopper | Principal Engineer | Developer Tools | Bangalore, IN | 2016-08-27 | On-call rotation for the ingest pipeline. Handles after-hours pages calmly. |
| 1027 | Donald Dijkstra | Principal Engineer | Developer Tools | Zurich, CH | 2017-09-08 | On-call rotation for the ingest pipeline. Handles after-hours pages calmly. |
| 1028 | Donald Hoare | Senior Engineer | Developer Tools | Zurich, CH | 2016-10-25 | Owns the migration from legacy auth middleware to OAuth2 — multi-quarter project. |
| 1029 | Alan Hoare | Researcher | Platform | Mountain View, CA | 2022-09-18 | Cross-functional partner with Product on the new metrics surface — strong communicator. |
WithMaxLines(N) relaxes the strict single-line rule when you want N lines per cell instead. Only meaningful in Excel mode. Range 1-5.
// Allow up to 2 lines per cell instead of strict single-line
var simple = new SimpleTable(data, CoreCss.BorderTable)
.WithScrollBehavior(TableScrollBehavior.Excel)
.WithMaxLines(2)
.DefineColumn("employee_name", "Name");
The Notes column has long text. Without WithMaxLines it would render as a very wide single line; here it wraps to 2 lines and clips.
| ID | Name | Role | Department | Location | Hired | Notes |
|---|---|---|---|---|---|---|
|
1000
|
Edsger Hopper
|
Engineer
|
Infrastructure
|
London, UK
|
2017-07-08
|
Internal-tools focus: built the deploy dashboard everyone uses, no fanfare.
|
|
1001
|
Donald Hopper
|
Tech Lead
|
Data Engineering
|
Cambridge, MA
|
2019-11-05
|
Owns the migration from legacy auth middleware to OAuth2 — multi-quarter project.
|
|
1002
|
Linus Turing
|
Researcher
|
Platform
|
Bangalore, IN
|
2020-07-13
|
On-call rotation for the ingest pipeline. Handles after-hours pages calmly.
|
|
1003
|
Grace Lovelace
|
Tech Lead
|
Developer Tools
|
Mountain View, CA
|
2015-06-04
|
Internal-tools focus: built the deploy dashboard everyone uses, no fanfare.
|
|
1004
|
Grace Stroustrup
|
Tech Lead
|
Infrastructure
|
London, UK
|
2015-05-26
|
Owns the migration from legacy auth middleware to OAuth2 — multi-quarter project.
|
|
1005
|
Donald Hoare
|
Engineer
|
Infrastructure
|
Bangalore, IN
|
2022-03-04
|
On-call rotation for the ingest pipeline. Handles after-hours pages calmly.
|
|
1006
|
Grace Lovelace
|
Engineer
|
Security
|
Zurich, CH
|
2016-02-08
|
Owns the migration from legacy auth middleware to OAuth2 — multi-quarter project.
|
|
1007
|
Barbara Turing
|
Researcher
|
Platform
|
London, UK
|
2020-10-16
|
Drove the bundle-size reduction effort, shipped a 40% improvement to first paint.
|
|
1008
|
Ada Hamilton
|
Tech Lead
|
Security
|
Cambridge, MA
|
2015-02-16
|
On-call rotation for the ingest pipeline. Handles after-hours pages calmly.
|
|
1009
|
Alan Lovelace
|
Tech Lead
|
Platform
|
Mountain View, CA
|
2015-02-12
|
Internal-tools focus: built the deploy dashboard everyone uses, no fanfare.
|
|
1010
|
Margaret Knuth
|
Architect
|
Platform
|
Bangalore, IN
|
2015-02-15
|
Mentors junior team members on distributed systems patterns and interview prep.
|
|
1011
|
Ada Dijkstra
|
Engineer
|
Developer Tools
|
Bangalore, IN
|
2019-06-03
|
On-call rotation for the ingest pipeline. Handles after-hours pages calmly.
|
|
1012
|
Edsger Lovelace
|
Architect
|
Platform
|
Mountain View, CA
|
2022-08-05
|
Drove the bundle-size reduction effort, shipped a 40% improvement to first paint.
|
|
1013
|
Margaret Turing
|
Principal Engineer
|
Infrastructure
|
Zurich, CH
|
2015-03-09
|
Cross-functional partner with Product on the new metrics surface — strong communicator.
|
|
1014
|
Bjarne Torvalds
|
Tech Lead
|
Platform
|
London, UK
|
2019-05-02
|
Owns the migration from legacy auth middleware to OAuth2 — multi-quarter project.
|
|
1015
|
Barbara Hopper
|
Engineer
|
Data Engineering
|
Mountain View, CA
|
2019-11-30
|
Cross-functional partner with Product on the new metrics surface — strong communicator.
|
|
1016
|
Barbara Dijkstra
|
Tech Lead
|
Data Engineering
|
Mountain View, CA
|
2024-06-26
|
Drove the bundle-size reduction effort, shipped a 40% improvement to first paint.
|
|
1017
|
Ada Torvalds
|
Principal Engineer
|
Platform
|
Cambridge, MA
|
2017-07-18
|
Mentors junior team members on distributed systems patterns and interview prep.
|
|
1018
|
Alan Knuth
|
Architect
|
Platform
|
Bangalore, IN
|
2016-02-29
|
Mentors junior team members on distributed systems patterns and interview prep.
|
|
1019
|
Linus Torvalds
|
Tech Lead
|
Developer Tools
|
London, UK
|
2015-05-28
|
Cross-functional partner with Product on the new metrics surface — strong communicator.
|
|
1020
|
Tony Turing
|
Principal Engineer
|
Developer Tools
|
Cambridge, MA
|
2020-03-23
|
Drove the bundle-size reduction effort, shipped a 40% improvement to first paint.
|
|
1021
|
Edsger Hopper
|
Senior Engineer
|
Platform
|
London, UK
|
2017-03-18
|
Cross-functional partner with Product on the new metrics surface — strong communicator.
|
|
1022
|
Bjarne Liskov
|
Engineer
|
Developer Tools
|
London, UK
|
2017-10-28
|
Drove the bundle-size reduction effort, shipped a 40% improvement to first paint.
|
|
1023
|
Donald Liskov
|
Engineer
|
Infrastructure
|
Cambridge, MA
|
2023-06-11
|
On-call rotation for the ingest pipeline. Handles after-hours pages calmly.
|
|
1024
|
Linus Stroustrup
|
Architect
|
Infrastructure
|
Bangalore, IN
|
2022-10-07
|
Mentors junior team members on distributed systems patterns and interview prep.
|
|
1025
|
Grace Lovelace
|
Senior Engineer
|
Developer Tools
|
Zurich, CH
|
2018-11-15
|
Drove the bundle-size reduction effort, shipped a 40% improvement to first paint.
|
|
1026
|
Margaret Hopper
|
Principal Engineer
|
Developer Tools
|
Bangalore, IN
|
2016-08-27
|
On-call rotation for the ingest pipeline. Handles after-hours pages calmly.
|
|
1027
|
Donald Dijkstra
|
Principal Engineer
|
Developer Tools
|
Zurich, CH
|
2017-09-08
|
On-call rotation for the ingest pipeline. Handles after-hours pages calmly.
|
|
1028
|
Donald Hoare
|
Senior Engineer
|
Developer Tools
|
Zurich, CH
|
2016-10-25
|
Owns the migration from legacy auth middleware to OAuth2 — multi-quarter project.
|
|
1029
|
Alan Hoare
|
Researcher
|
Platform
|
Mountain View, CA
|
2022-09-18
|
Cross-functional partner with Product on the new metrics surface — strong communicator.
|
Sticky Header: WithStickyHeader
Pin the header row in place while the body scrolls — the same effect as Excel's "Freeze Top Row". The table is wrapped in a scroll container so the scrollbar belongs to the table, not the page. This is what you want for long lists embedded in a dashboard panel: the surrounding chrome (filters, KPIs, page header) stays visible while the user scrolls through hundreds of rows.
// Sticky header that fills its parent panel
var simple = new SimpleTable(data, CoreCss.BorderTable)
.WithStickyHeader()
.DefineColumn("employee_name", "Name")
.DefineColumn("role", "Role");
By default the wrapper uses flex:1 to fill its parent container vertically — this works when the table sits inside a panel-fill or panel-col. Pass any CSS length to override with a fixed height instead.
The demo box below is a 250px-tall flex column, mimicking a dashboard panel. The table fills it via flex:1; scroll inside the box to see the header stay put.
| ID | Name | Role |
|---|---|---|
| 1000 | Edsger Hopper | Engineer |
| 1001 | Donald Hopper | Tech Lead |
| 1002 | Linus Turing | Researcher |
| 1003 | Grace Lovelace | Tech Lead |
| 1004 | Grace Stroustrup | Tech Lead |
| 1005 | Donald Hoare | Engineer |
| 1006 | Grace Lovelace | Engineer |
| 1007 | Barbara Turing | Researcher |
| 1008 | Ada Hamilton | Tech Lead |
| 1009 | Alan Lovelace | Tech Lead |
| 1010 | Margaret Knuth | Architect |
| 1011 | Ada Dijkstra | Engineer |
| 1012 | Edsger Lovelace | Architect |
| 1013 | Margaret Turing | Principal Engineer |
| 1014 | Bjarne Torvalds | Tech Lead |
| 1015 | Barbara Hopper | Engineer |
| 1016 | Barbara Dijkstra | Tech Lead |
| 1017 | Ada Torvalds | Principal Engineer |
| 1018 | Alan Knuth | Architect |
| 1019 | Linus Torvalds | Tech Lead |
| 1020 | Tony Turing | Principal Engineer |
| 1021 | Edsger Hopper | Senior Engineer |
| 1022 | Bjarne Liskov | Engineer |
| 1023 | Donald Liskov | Engineer |
| 1024 | Linus Stroustrup | Architect |
| 1025 | Grace Lovelace | Senior Engineer |
| 1026 | Margaret Hopper | Principal Engineer |
| 1027 | Donald Dijkstra | Principal Engineer |
| 1028 | Donald Hoare | Senior Engineer |
| 1029 | Alan Hoare | Researcher |
// Fixed-height scroll region (any CSS length is accepted)
.WithStickyHeader("600px")
.WithStickyHeader("70vh")
.WithStickyHeader("calc(100vh - 200px)")
Same data, fixed 200px height — no flex parent needed.
| ID | Name | Role |
|---|---|---|
| 1000 | Edsger Hopper | Engineer |
| 1001 | Donald Hopper | Tech Lead |
| 1002 | Linus Turing | Researcher |
| 1003 | Grace Lovelace | Tech Lead |
| 1004 | Grace Stroustrup | Tech Lead |
| 1005 | Donald Hoare | Engineer |
| 1006 | Grace Lovelace | Engineer |
| 1007 | Barbara Turing | Researcher |
| 1008 | Ada Hamilton | Tech Lead |
| 1009 | Alan Lovelace | Tech Lead |
| 1010 | Margaret Knuth | Architect |
| 1011 | Ada Dijkstra | Engineer |
| 1012 | Edsger Lovelace | Architect |
| 1013 | Margaret Turing | Principal Engineer |
| 1014 | Bjarne Torvalds | Tech Lead |
| 1015 | Barbara Hopper | Engineer |
| 1016 | Barbara Dijkstra | Tech Lead |
| 1017 | Ada Torvalds | Principal Engineer |
| 1018 | Alan Knuth | Architect |
| 1019 | Linus Torvalds | Tech Lead |
| 1020 | Tony Turing | Principal Engineer |
| 1021 | Edsger Hopper | Senior Engineer |
| 1022 | Bjarne Liskov | Engineer |
| 1023 | Donald Liskov | Engineer |
| 1024 | Linus Stroustrup | Architect |
| 1025 | Grace Lovelace | Senior Engineer |
| 1026 | Margaret Hopper | Principal Engineer |
| 1027 | Donald Dijkstra | Principal Engineer |
| 1028 | Donald Hoare | Senior Engineer |
| 1029 | Alan Hoare | Researcher |
Bounded Height: WithMaxHeight
If you want a scrollable table-scoped scrollbar but DON'T want the sticky-header treatment (header scrolls with the body), use WithMaxHeight instead. Same wrapper, no sticky positioning, no border-collapse change. Useful when the regular border-table look matters more than keeping headers visible — for example, a short reference table you want to bound at 300px without changing its appearance.
// Plain bordered table, capped at 200px tall
var simple = new SimpleTable(data, CoreCss.BorderTable)
.WithMaxHeight("200px")
.DefineColumn("employee_name", "Name")
.DefineColumn("role", "Role");
Same table as the sticky-fixed demo above, but the header scrolls with the body. Notice the borders use border-collapse:collapse like a normal border-table — no visual difference from a non-scrolled border-table other than the scroll behavior.
| ID | Name | Role |
|---|---|---|
| 1000 | Edsger Hopper | Engineer |
| 1001 | Donald Hopper | Tech Lead |
| 1002 | Linus Turing | Researcher |
| 1003 | Grace Lovelace | Tech Lead |
| 1004 | Grace Stroustrup | Tech Lead |
| 1005 | Donald Hoare | Engineer |
| 1006 | Grace Lovelace | Engineer |
| 1007 | Barbara Turing | Researcher |
| 1008 | Ada Hamilton | Tech Lead |
| 1009 | Alan Lovelace | Tech Lead |
| 1010 | Margaret Knuth | Architect |
| 1011 | Ada Dijkstra | Engineer |
| 1012 | Edsger Lovelace | Architect |
| 1013 | Margaret Turing | Principal Engineer |
| 1014 | Bjarne Torvalds | Tech Lead |
| 1015 | Barbara Hopper | Engineer |
| 1016 | Barbara Dijkstra | Tech Lead |
| 1017 | Ada Torvalds | Principal Engineer |
| 1018 | Alan Knuth | Architect |
| 1019 | Linus Torvalds | Tech Lead |
| 1020 | Tony Turing | Principal Engineer |
| 1021 | Edsger Hopper | Senior Engineer |
| 1022 | Bjarne Liskov | Engineer |
| 1023 | Donald Liskov | Engineer |
| 1024 | Linus Stroustrup | Architect |
| 1025 | Grace Lovelace | Senior Engineer |
| 1026 | Margaret Hopper | Principal Engineer |
| 1027 | Donald Dijkstra | Principal Engineer |
| 1028 | Donald Hoare | Senior Engineer |
| 1029 | Alan Hoare | Researcher |
WithMaxHeight composes with WithStickyHeader and WithScrollBehavior(Excel) the same way maxHeight does as an argument to WithStickyHeader. The two methods can be chained in either order — the explicit value wins, an unspecified argument leaves any prior value intact.
Composing with Excel Mode
WithStickyHeader and WithScrollBehavior(Excel) are orthogonal and fully composable. The sticky wrapper handles both X and Y scroll, so when both are active you get nowrap single-line cells, horizontal scroll, vertical scroll, and a sticky header all together. This is the closest match to a native Excel grid — the table-scoped scrollbars combined with frozen headers.
// Wide table + sticky header — full Excel-grid feel
var simple = new SimpleTable(data, CoreCss.BorderTable)
.WithScrollBehavior(TableScrollBehavior.Excel)
.WithStickyHeader()
.DefineColumn("employee_name", "Name")
.DefineColumn("role", "Role")
.DefineColumn("department", "Department")
.DefineColumn("hire_date", "Hired");
Scroll right (wide data, X-scroll) AND down (sticky header, Y-scroll) — both scrollbars belong to the table, the page chrome stays put.
| ID | Name | Role | Department | Location | Hired | Notes |
|---|---|---|---|---|---|---|
| 1000 | Edsger Hopper | Engineer | Infrastructure | London, UK | 2017-07-08 | Internal-tools focus: built the deploy dashboard everyone uses, no fanfare. |
| 1001 | Donald Hopper | Tech Lead | Data Engineering | Cambridge, MA | 2019-11-05 | Owns the migration from legacy auth middleware to OAuth2 — multi-quarter project. |
| 1002 | Linus Turing | Researcher | Platform | Bangalore, IN | 2020-07-13 | On-call rotation for the ingest pipeline. Handles after-hours pages calmly. |
| 1003 | Grace Lovelace | Tech Lead | Developer Tools | Mountain View, CA | 2015-06-04 | Internal-tools focus: built the deploy dashboard everyone uses, no fanfare. |
| 1004 | Grace Stroustrup | Tech Lead | Infrastructure | London, UK | 2015-05-26 | Owns the migration from legacy auth middleware to OAuth2 — multi-quarter project. |
| 1005 | Donald Hoare | Engineer | Infrastructure | Bangalore, IN | 2022-03-04 | On-call rotation for the ingest pipeline. Handles after-hours pages calmly. |
| 1006 | Grace Lovelace | Engineer | Security | Zurich, CH | 2016-02-08 | Owns the migration from legacy auth middleware to OAuth2 — multi-quarter project. |
| 1007 | Barbara Turing | Researcher | Platform | London, UK | 2020-10-16 | Drove the bundle-size reduction effort, shipped a 40% improvement to first paint. |
| 1008 | Ada Hamilton | Tech Lead | Security | Cambridge, MA | 2015-02-16 | On-call rotation for the ingest pipeline. Handles after-hours pages calmly. |
| 1009 | Alan Lovelace | Tech Lead | Platform | Mountain View, CA | 2015-02-12 | Internal-tools focus: built the deploy dashboard everyone uses, no fanfare. |
| 1010 | Margaret Knuth | Architect | Platform | Bangalore, IN | 2015-02-15 | Mentors junior team members on distributed systems patterns and interview prep. |
| 1011 | Ada Dijkstra | Engineer | Developer Tools | Bangalore, IN | 2019-06-03 | On-call rotation for the ingest pipeline. Handles after-hours pages calmly. |
| 1012 | Edsger Lovelace | Architect | Platform | Mountain View, CA | 2022-08-05 | Drove the bundle-size reduction effort, shipped a 40% improvement to first paint. |
| 1013 | Margaret Turing | Principal Engineer | Infrastructure | Zurich, CH | 2015-03-09 | Cross-functional partner with Product on the new metrics surface — strong communicator. |
| 1014 | Bjarne Torvalds | Tech Lead | Platform | London, UK | 2019-05-02 | Owns the migration from legacy auth middleware to OAuth2 — multi-quarter project. |
| 1015 | Barbara Hopper | Engineer | Data Engineering | Mountain View, CA | 2019-11-30 | Cross-functional partner with Product on the new metrics surface — strong communicator. |
| 1016 | Barbara Dijkstra | Tech Lead | Data Engineering | Mountain View, CA | 2024-06-26 | Drove the bundle-size reduction effort, shipped a 40% improvement to first paint. |
| 1017 | Ada Torvalds | Principal Engineer | Platform | Cambridge, MA | 2017-07-18 | Mentors junior team members on distributed systems patterns and interview prep. |
| 1018 | Alan Knuth | Architect | Platform | Bangalore, IN | 2016-02-29 | Mentors junior team members on distributed systems patterns and interview prep. |
| 1019 | Linus Torvalds | Tech Lead | Developer Tools | London, UK | 2015-05-28 | Cross-functional partner with Product on the new metrics surface — strong communicator. |
| 1020 | Tony Turing | Principal Engineer | Developer Tools | Cambridge, MA | 2020-03-23 | Drove the bundle-size reduction effort, shipped a 40% improvement to first paint. |
| 1021 | Edsger Hopper | Senior Engineer | Platform | London, UK | 2017-03-18 | Cross-functional partner with Product on the new metrics surface — strong communicator. |
| 1022 | Bjarne Liskov | Engineer | Developer Tools | London, UK | 2017-10-28 | Drove the bundle-size reduction effort, shipped a 40% improvement to first paint. |
| 1023 | Donald Liskov | Engineer | Infrastructure | Cambridge, MA | 2023-06-11 | On-call rotation for the ingest pipeline. Handles after-hours pages calmly. |
| 1024 | Linus Stroustrup | Architect | Infrastructure | Bangalore, IN | 2022-10-07 | Mentors junior team members on distributed systems patterns and interview prep. |
| 1025 | Grace Lovelace | Senior Engineer | Developer Tools | Zurich, CH | 2018-11-15 | Drove the bundle-size reduction effort, shipped a 40% improvement to first paint. |
| 1026 | Margaret Hopper | Principal Engineer | Developer Tools | Bangalore, IN | 2016-08-27 | On-call rotation for the ingest pipeline. Handles after-hours pages calmly. |
| 1027 | Donald Dijkstra | Principal Engineer | Developer Tools | Zurich, CH | 2017-09-08 | On-call rotation for the ingest pipeline. Handles after-hours pages calmly. |
| 1028 | Donald Hoare | Senior Engineer | Developer Tools | Zurich, CH | 2016-10-25 | Owns the migration from legacy auth middleware to OAuth2 — multi-quarter project. |
| 1029 | Alan Hoare | Researcher | Platform | Mountain View, CA | 2022-09-18 | Cross-functional partner with Product on the new metrics surface — strong communicator. |
Why border-collapse Matters
CSS sticky positioning interacts badly with border-collapse:collapse (the default). Collapsed borders are shared between adjacent cells, so the bottom border of a sticky <th> is partly owned by the first body <td> — meaning that border scrolls away with the body, leaving the header without a bottom edge. WithStickyHeader switches the table to border-collapse:separate and re-draws the borders so each visible line is owned by exactly one cell. You don't need to do anything to enable this — it's automatic when you add the sticky-header class. But it's why the visual border behavior changes slightly compared to a plain border-table.