Skip to content

Framework7 Data Table and Grid — Tabular Data, Sorting, and Responsive Layout

DodaTech Updated 2026-06-28 8 min read

In this tutorial, you will learn about Framework7 Data Table and Grid. We cover key concepts, practical examples, and best practices to help you master this topic.

Framework7 data tables display tabular data with sorting, pagination, row selection, and responsive behavior that collapses columns on small screens, while CSS grids provide flexible layout containers.

What You'll Learn

  • Creating responsive data tables
  • Column sorting and pagination
  • Row selection and actions
  • Responsive table collapse
  • CSS Grid layouts

Why It Matters

Mobile data tables must adapt to limited screen width — showing essential columns, collapsing details, and providing sort/filter without desktop-style table interactions. Framework7 data tables handle responsive collapse automatically.

Real-World Use

An order management table on mobile showing order ID, status, and amount as columns, with other fields collapsed behind an expand button, and column sorting for date and amount.

Data Table Architecture

flowchart TD
    A[Data Table] --> B[Header Row]
    A --> C[Data Rows]
    A --> D[Footer]
    B --> E[Sortable Columns]
    C --> F[Selectable Rows]
    C --> G[Collapsible Cells]
    A --> H[Pagination]
    A --> I[Search/Filter]
    style A fill:#e6f3ff,stroke:#4a90d9,stroke-width:2px

Basic Data Table

<div class="data-table">
  <table>
    <thead>
      <tr>
        <th class="sortable-cell">Name</th>
        <th class="sortable-cell sortable-cell-active sortable-cell-asc">Position</th>
        <th class="sortable-cell">Office</th>
        <th>Age</th>
        <th>Salary</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td class="label-cell">Alice Johnson</td>
        <td class="numeric-cell">Senior Developer</td>
        <td>New York</td>
        <td class="numeric-cell">32</td>
        <td class="numeric-cell">$95,000</td>
      </tr>
      <tr>
        <td class="label-cell">Bob Smith</td>
        <td class="numeric-cell">Product Manager</td>
        <td>San Francisco</td>
        <td class="numeric-cell">28</td>
        <td class="numeric-cell">$110,000</td>
      </tr>
      <tr>
        <td class="label-cell">Charlie Brown</td>
        <td class="numeric-cell">Designer</td>
        <td>Chicago</td>
        <td class="numeric-cell">35</td>
        <td class="numeric-cell">$85,000</td>
      </tr>
    </tbody>
  </table>
</div>

Expected output: A styled data table with sortable column headers. The Position column is active with ascending sort indicator. Table has striped rows by default.

Sortable Table

<div class="data-table" id="sortable-table">
  <table>
    <thead>
      <tr>
        <th class="sortable-cell" data-sort="name">Name</th>
        <th class="sortable-cell" data-sort="position">Position</th>
        <th class="sortable-cell" data-sort="office">Office</th>
        <th class="sortable-cell" data-sort="age">Age</th>
        <th class="sortable-cell" data-sort="salary">Salary</th>
      </tr>
    </thead>
    <tbody>
      <!-- rows -->
    </tbody>
  </table>
</div>
var tableData = [
  { name: 'Alice Johnson', position: 'Senior Developer', office: 'New York', age: 32, salary: 95000 },
  { name: 'Bob Smith', position: 'Product Manager', office: 'San Francisco', age: 28, salary: 110000 },
  { name: 'Charlie Brown', position: 'Designer', office: 'Chicago', age: 35, salary: 85000 }
];

var currentSort = { field: 'name', dir: 'asc' };

function renderTable(data) {
  var tbody = $$('#sortable-table tbody');
  tbody.html('');
  data.forEach(function(row) {
    tbody.append('<tr>' +
      '<td class="label-cell">' + row.name + '</td>' +
      '<td>' + row.position + '</td>' +
      '<td>' + row.office + '</td>' +
      '<td class="numeric-cell">' + row.age + '</td>' +
      '<td class="numeric-cell">$' + row.salary.toLocaleString() + '</td>' +
    '</tr>');
  });
}

function sortData(field, dir) {
  return tableData.sort(function(a, b) {
    if (dir === 'asc') {
      return a[field] > b[field] ? 1 : -1;
    } else {
      return a[field] < b[field] ? 1 : -1;
    }
  });
}

$$('#sortable-table .sortable-cell').on('click', function() {
  var field = this.getAttribute('data-sort');
  var dir = 'asc';

  if (this.classList.contains('sortable-cell-active')) {
    dir = this.classList.contains('sortable-cell-asc') ? 'desc' : 'asc';
  }

  // Reset all headers
  $$('#sortable-table .sortable-cell').removeClass('sortable-cell-active sortable-cell-asc sortable-cell-desc');

  // Set active
  this.classList.add('sortable-cell-active');
  this.classList.add('sortable-cell-' + dir);

  currentSort = { field: field, dir: dir };
  var sorted = sortData(field, dir);
  renderTable(sorted);
});

renderTable(tableData);

Expected output: Clicking a column header sorts the table by that column. Clicking again reverses the sort direction. Active column shows an arrow indicator.

Table with Selection

<div class="data-table" id="selectable-table">
  <div class="data-table-header">
    <div class="data-table-title">Users</div>
    <div class="data-table-actions">
      <a href="#" class="link" id="delete-selected">Delete</a>
    </div>
  </div>
  <div class="data-table-header-selected">
    <div class="data-table-title-selected">
      <span id="selected-count">0</span> items selected
    </div>
    <div class="data-table-actions-selected">
      <a href="#" class="link" id="clear-selection">Cancel</a>
    </div>
  </div>
  <table>
    <thead>
      <tr>
        <th class="checkbox-cell">
          <label class="checkbox"><input type="checkbox" id="select-all" /><i class="icon-checkbox"></i></label>
        </th>
        <th>Name</th>
        <th>Email</th>
        <th>Role</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td class="checkbox-cell">
          <label class="checkbox"><input type="checkbox" class="select-row" /><i class="icon-checkbox"></i></label>
        </td>
        <td class="label-cell">Alice</td>
        <td>alice@example.com</td>
        <td>Admin</td>
      </tr>
    </tbody>
  </table>
</div>
// Select all toggle
$$('#select-all').on('change', function() {
  var isChecked = this.checked;
  $$('.select-row').each(function() {
    this.checked = isChecked;
  });
  updateSelectionUI();
});

// Individual selection
$$(document).on('change', '.select-row', function() {
  updateSelectionUI();
});

function updateSelectionUI() {
  var count = $$('.select-row:checked').length;
  $$('#selected-count').text(count);
  if (count > 0) {
    $$('#selectable-table').addClass('data-table-has-selected');
  } else {
    $$('#selectable-table').removeClass('data-table-has-selected');
  }
}

// Delete selected
$$('#delete-selected').on('click', function() {
  var selected = [];
  $$('.select-row:checked').each(function() {
    selected.push($$(this).parents('tr').index());
  });
  console.log('Delete rows:', selected);
  app.dialog.alert('Deleted ' + selected.length + ' rows');
});

Expected output: Checking rows shows a selected header with count and actions. Select all toggles all rows. The data-table-has-selected class switches the header to show selected-state UI.

Responsive Table Collapse

<!-- Add data-table-collapsible to make columns collapse on mobile -->
<div class="data-table data-table-collapsible">
  <table>
    <thead>
      <tr>
        <th>Name</th>
        <th data-collapsible="hidden">Position</th>
        <th data-collapsible="hidden">Office</th>
        <th data-collapsible="hidden">Email</th>
        <th data-collapsible="hidden">Phone</th>
        <th data-collapsible="hidden">Age</th>
        <th>Status</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td class="label-cell">
          Alice Johnson
          <span class="data-table-cell-content" style="display:none">
            <span><b>Position:</b> Senior Developer</span>
            <span><b>Office:</b> New York</span>
            <span><b>Email:</b> alice@example.com</span>
            <span><b>Phone:</b> +1 555-0101</span>
            <span><b>Age:</b> 32</span>
          </span>
        </td>
        <td class="numeric-cell">Senior Developer</td>
        <td>New York</td>
        <td>alice@example.com</td>
        <td>+1 555-0101</td>
        <td class="numeric-cell">32</td>
        <td><span class="badge badge-success">Active</span></td>
      </tr>
    </tbody>
  </table>
</div>

Expected output: On screens below 768px, columns with data-collapsible="hidden" hide and their content appears in an expandable section under the first column. Tapping a row expands to show all fields.

Table with Pagination

<div class="data-table">
  <table>
    <!-- table content -->
  </table>
  <div class="data-table-footer">
    <div class="data-table-info">Showing 1-10 of 247</div>
    <div class="data-table-pagination">
      <a href="#" class="link"><i class="icon f7-icons">chevron-left</i></a>
      <span class="data-table-pagination-current">1</span>
      <span>/</span>
      <span class="data-table-pagination-total">25</span>
      <a href="#" class="link"><i class="icon f7-icons">chevron-right</i></a>
    </div>
  </div>
</div>
// Pagination click handlers
var currentPage = 1;
var totalPages = 25;

function goToPage(page) {
  if (page < 1 || page > totalPages) return;
  currentPage = page;
  $$('.data-table-pagination-current').text(page);
  // Load data for page
  console.log('Loading page', page);
  // fetch('/api/users?page=' + page)...
}

$$('.data-table-pagination .link').eq(0).on('click', function() {
  goToPage(currentPage - 1);
});

$$('.data-table-pagination .link').eq(1).on('click', function() {
  goToPage(currentPage + 1);
});

Expected output: Pagination controls show current page, total pages, and prev/next buttons. The data-table-info shows the range of displayed records.

CSS Grid Layout

<!-- Framework7 uses a 12-column grid system -->
<div class="row">
  <div class="col-50">50% width</div>
  <div class="col-50">50% width</div>
</div>

<div class="row">
  <div class="col-33">33.33%</div>
  <div class="col-33">33.33%</div>
  <div class="col-33">33.33%</div>
</div>

<div class="row">
  <div class="col-25">25%</div>
  <div class="col-25">25%</div>
  <div class="col-25">25%</div>
  <div class="col-25">25%</div>
</div>

<div class="row">
  <div class="col-60">60%</div>
  <div class="col-40">40%</div>
</div>

<!-- Nested grids -->
<div class="row">
  <div class="col-50">
    <div class="row">
      <div class="col-50">25% of parent</div>
      <div class="col-50">25% of parent</div>
    </div>
  </div>
  <div class="col-50">50% of parent</div>
</div>

<!-- Responsive grid: 100% on small screens, 50% on medium+ -->
<div class="row">
  <div class="col-100 medium-50">Responsive column</div>
  <div class="col-100 medium-50">Responsive column</div>
</div>

<!-- Grid with gaps -->
<div class="row" style="gap:10px">
  <div class="col-50" style="padding:10px;background:#f5f5f5">Card 1</div>
  <div class="col-50" style="padding:10px;background:#f5f5f5">Card 2</div>
</div>

Expected output: Grid columns divide the row proportionally. Responsive classes change layout at breakpoints. Nested grids create complex layouts.

Data Table with Inputs

<div class="data-table">
  <table>
    <thead>
      <tr>
        <th>Product</th>
        <th>Price</th>
        <th>Quantity</th>
        <th>Total</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td class="label-cell">Laptop</td>
        <td class="numeric-cell">$999.99</td>
        <td class="numeric-cell">
          <div class="stepper" data-value="1" data-min="0" data-max="99">
            <div class="stepper-button-minus"></div>
            <div class="stepper-input-wrap">
              <input type="text" value="1" readonly />
            </div>
            <div class="stepper-button-plus"></div>
          </div>
        </td>
        <td class="numeric-cell">$999.99</td>
      </tr>
      <tr>
        <td class="label-cell">Monitor</td>
        <td class="numeric-cell">$399.99</td>
        <td class="numeric-cell">
          <div class="stepper" data-value="2" data-min="0" data-max="99">
            <div class="stepper-button-minus"></div>
            <div class="stepper-input-wrap">
              <input type="text" value="2" readonly />
            </div>
            <div class="stepper-button-plus"></div>
          </div>
        </td>
        <td class="numeric-cell">$799.98</td>
      </tr>
    </tbody>
    <tfoot>
      <tr>
        <td colspan="3" class="numeric-cell" style="font-weight:bold">Total</td>
        <td class="numeric-cell" style="font-weight:bold" id="cart-total">$1,799.97</td>
      </tr>
    </tfoot>
  </table>
</div>
// Update total when stepper changes
$$(document).on('stepper:change', '.stepper', function(e) {
  var newValue = e.detail.value;
  var row = $$(this).parents('tr');
  var priceText = row.find('td').eq(1).text();
  var price = parseFloat(priceText.replace('$', ''));
  var total = (price * newValue).toFixed(2);
  row.find('td').eq(3).text('$' + total);
  updateCartTotal();
});

function updateCartTotal() {
  var grandTotal = 0;
  $$('#cart-table tbody tr').each(function() {
    var totalText = $$(this).find('td').eq(3).text();
    grandTotal += parseFloat(totalText.replace('$', ''));
  });
  $$('#cart-total').text('$' + grandTotal.toFixed(2));
}

Expected output: An order table with stepper inputs for quantity. Changing quantity updates the row total and the grand total in real time.

Common Mistakes

  1. Not using label-cell and numeric-cell classes - Cell alignment classes ensure proper text alignment (left for labels, right for numbers). Without them, numeric columns look misaligned.

  2. Forgetting thead and tbody - Framework7 table styling targets thead and tbody elements. Tables without proper structure lose styling.

  3. Overusing collapsible tables - Data-table-collapsible is great for mobile but unnecessary on desktop. Use responsive classes or JavaScript to enable collapse only on small screens.

  4. Using tables for layout instead of grid - HTML tables are for data. Use Framework7's CSS grid (row/col classes) for page layout, not data tables.

  5. Not handling zero-result states - An empty table shows only headers. Add a "No data" row or message when there are zero results to display.

Practice Questions

  1. How do you make a table column sortable?
  2. How do you enable row selection with checkboxes?
  3. What does data-collapsible="hidden" do on mobile?
  4. How do you add pagination to a data table?
  5. What grid classes are available for responsive layouts?

Challenge: Build a product inventory table with: sortable columns (name, price, stock, category), row selection with checkbox and bulk delete, responsive collapse hiding description and supplier columns on mobile, pagination with 10 items per page, inline stepper controls for stock adjustment, and a grid-based stats summary above the table.

FAQ

Can I use Framework7 tables with dynamic data from an API?

Yes. Use fetch() to load data, then build the table rows with JavaScript. Framework7 does not have a built-in data table component — you build the HTML and use CSS classes for styling.

How do I make tables scroll horizontally on very narrow screens?

Wrap the table in a container with overflow-x: auto. Framework7's data-table component scrolls naturally when content overflows.

Can I export table data to CSV?

Yes. Iterate the table rows and columns with JavaScript, build a CSV string, and trigger a download using a Blob and URL.createObjectURL().

How do I highlight a selected row?

Add a click handler that adds an 'active' class to the clicked tr. Style .data-table tbody tr.active with your highlight color.

Does Framework7 have an advanced data grid like Ext JS?

No. Framework7 focuses on mobile-optimized UI. For advanced data grids with virtualization and inline editing, use a dedicated grid library alongside Framework7.

Mini Project

Build an admin dashboard table for order management with: sortable columns (order ID, customer, date, status, amount), responsive collapse hiding customer email and shipping address on mobile, row selection for bulk actions, pagination, inline stepper for quantity edits, status badges with color coding, and a grid-based summary showing total orders, revenue, and pending count.

What's Next

Data tables display structured information. Learn how Framework7 Color Themes customize the app appearance with built-in colors, custom CSS variables, and dynamic theming.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro