Markup & Template Structure

TableCrafter renders tables without overridable PHP partials. Instead it emits a predictable, class-driven HTML skeleton from the [tablecrafter] shortcode and progressively enhances it in the browser, so you customize the output by targeting its stable CSS classes and data attributes.

Shortcode SSR + Hydration CSS Classes Data Attributes JS Events

How rendering works

There is one shortcode, [tablecrafter], registered in tablecrafter.php and bound to the render_table() method. The Gutenberg block and the Elementor widget both funnel into this same method, so every integration produces identical markup.

Rendering happens in two phases:

  1. Server-side (SSR): On first render PHP fetches the source, builds a crawlable <table class="tc-table">, caches the HTML in a transient, and prints it inside the container. The same HTML is reused on subsequent loads via a stale-while-revalidate transient cache.
  2. Client-side (hydration): frontend.js finds every .tablecrafter-container, reads its data-* attributes, and instantiates the TableCrafter class from tablecrafter.js. It wraps the SSR table in a .tc-wrapper and injects the interactive toolbar, pagination, and refresh UI around it.
ℹ️

TableCrafter ships no theme-overridable template files (no templates/ folder, no locate_template() lookup). Customization is done through CSS classes and the JS event API documented below, not by copying partials into your theme.

The outer container

The shortcode always outputs a single wrapper <div> whose data-* attributes carry every shortcode setting forward to the JavaScript. This is the integration surface for both styling and scripting.

<div id="tc-block-1a2b3c" class="tablecrafter-container"
     data-source="https://example.com/data.json"
     data-include="" data-exclude="" data-root=""
     data-search="true" data-filters="true" data-export="false"
     data-per-page="25" data-sort="price:desc"
     data-auto-refresh="false" data-refresh-interval="300000"
     data-ssr="true">
  <!-- server-rendered <table> goes here -->
  <script type="application/json" class="tc-initial-data">[...]</script>
</div>

Two details worth noting: the initial dataset is serialized into an inline <script type="application/json" class="tc-initial-data"> tag so the JS can hydrate without a second network request, and data-ssr="true" tells the JS that server markup already exists and should be wrapped rather than replaced.

Container data attributes

These attributes mirror the shortcode attributes one-to-one. Override them by editing the shortcode; the JS reads them at init time.

AttributeSource attrPurpose
data-sourcesourceJSON/CSV/Google Sheet URL. Required for any output. Required
data-searchsearchRenders the .tc-global-search-container input. Optional
data-filtersfiltersRenders the .tc-filters per-column controls. Optional
data-exportexportRenders .tc-export-controls (CSV/XLSX/PDF + copy). Optional
data-per-pageper_pagePage size; 0 disables pagination. Optional
data-sortsortcolumn:direction initial sort, applied server-side. Optional
data-include / data-excludeinclude / excludeComma-separated column allow/deny lists. Optional
data-rootrootDot-path to the array inside a nested JSON response. Optional
data-auto-refresh / data-refresh-intervalauto_refresh / refresh_intervalEnables the live .tc-refresh-indicator widget. Optional

Server-rendered table markup

The fetch_and_render_php() method produces semantic, accessible table HTML. Headers carry sorting hooks (tabindex, aria-sort, data-field) and each cell carries a data-tc-label attribute mirroring its column header, which the JS uses when collapsing to mobile cards.

<table class="tc-table">
  <thead><tr>
    <th class="tc-sortable" tabindex="0"
        aria-sort="descending" data-field="price">Price</th>
  </tr></thead>
  <tbody>
    <tr>
      <td data-tc-label="Price">$42.00</td>
    </tr>
  </tbody>
</table>

All server-generated HTML is passed through sanitize_table_html() (a wp_kses allow-list) before printing, so any custom markup must use the supported tags: table, thead, tbody, tr, th, td, a, img, span, time, div, strong, em, small with their whitelisted attributes only.

Smart cell value formatting

Rather than printing raw strings, render_value_php() detects the value type and wraps it in a recognizable element you can target with CSS:

Detected typeOutput markup
Boolean true/false<span class="tc-badge tc-yes">Yes</span> / tc-no
Image URL<img loading="lazy"> (max 100px wide)
Email<a href="mailto:…">
ISO date<time datetime="…">
HTTP(S) URL<a target="_blank" rel="noopener noreferrer"> (truncated text)
Array of values<div class="tc-tag-list"> of <span class="tc-tag">, with tc-tag tc-more overflow chip
💡

To restyle "Yes/No" pills, link colors, or tag chips, target .tc-badge, .tc-table a, and .tc-tag in your theme CSS. These classes are stable across both the SSR table and the client-rebuilt table.

Client-built toolbar & controls

During hydration the JS injects interactive controls into the .tc-wrapper in a fixed order. Each is a discrete, classed block, so you can show, hide, or reposition them with CSS:

Responsive card view

On narrow viewports the JS swaps the table for a card layout instead of horizontally scrolling. The structure is .tc-cards-container[role="list"] > .tc-card[role="listitem"], each with a .tc-card-header, a .tc-card-body of .tc-card-field rows (.tc-card-label + .tc-card-value), and an optional .tc-card-toggle expander (.tc-card-expandable / .tc-card-expanded) for fields hidden at the current breakpoint.

Auto-refresh indicator

When auto_refresh is enabled the JS appends a .tc-refresh-indicator widget containing .tc-refresh-status (.tc-refresh-icon, .tc-refresh-text, optional .tc-countdown and .tc-last-updated spans) plus button.tc-refresh-toggle and button.tc-refresh-manual. The paused state adds a .paused class to the indicator.

Theming with CSS

The stylesheet (assets/css/tablecrafter.css, enqueued as handle tablecrafter-style) uses hard-coded color values rather than a public set of CSS custom properties, so theme overrides are done by targeting classes. The one place variables appear is a high-contrast mode: adding .tc-high-contrast to .tc-wrapper activates the --tc-border-color, --tc-text-color, --tc-bg-color, and --tc-focus-color variables.

/* Recolor headers and the sortable cursor */
.tc-table th { background-color: #0f172a; color: #fff; }
.tc-table th.tc-sortable:hover { background-color: #1e293b; }

/* Hide the copy-to-clipboard button but keep export */
.tablecrafter-container .tc-copy-clipboard { display: none; }
⚠️

Scope overrides to .tablecrafter-container (or a wrapping element). The tc-* classes are shared by every table on the page, so unscoped rules affect all of them.

JavaScript events

For card interactions the instance dispatches bubbling CustomEvents on the container, namespaced under tablecrafter:. Listen on .tablecrafter-container to react without forking the plugin.

const el = document.querySelector('.tablecrafter-container');
el.addEventListener('tablecrafter:cardTap', (e) => {
  console.log(e.detail.rowData, e.detail.rowIndex);
});
// Also available: tablecrafter:cardView, tablecrafter:cardEdit

Generating a shortcode

You rarely hand-write these attributes. The admin screen at WP Admin → TableCrafter (menu slug tablecrafter-wp-data-tables) provides a live-preview playground and a copy-ready shortcode builder. A typical generated tag looks like:

[tablecrafter source="https://example.com/products.json" search="true" filters="true" export="true" per_page="25" sort="price:desc"]

Next steps: see shortcode-attributes.html for the complete attribute reference, and styling-and-theming.html for a full class-by-class CSS targeting guide.