PHPackages                             donquixote/cellbrush - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Templating &amp; Views](/categories/templating)
4. /
5. donquixote/cellbrush

ActiveLibrary[Templating &amp; Views](/categories/templating)

donquixote/cellbrush
====================

Library to generate HTML tables.

1.1.0(1y ago)4897.6k↓25%12[2 issues](https://github.com/donquixote/cellbrush/issues)[1 PRs](https://github.com/donquixote/cellbrush/pulls)3MITPHP

Since Nov 13Pushed 1y ago5 watchersCompare

[ Source](https://github.com/donquixote/cellbrush)[ Packagist](https://packagist.org/packages/donquixote/cellbrush)[ Docs](http://github.com/donquixote/cellbrush)[ RSS](/packages/donquixote-cellbrush/feed)WikiDiscussions 1.x Synced 1mo ago

READMEChangelog (1)Dependencies (1)Versions (19)Used By (3)

Cellbrush: Generate HTML tables with PHP
========================================

[](#cellbrush-generate-html-tables-with-php)

A library to generate complex HTML tables with PHP, with support for rowspan and colspan.

*If you read this in packagist, some parts fo this README are not visible. Go to [github](https://github.com/donquixote/cellbrush) instead.*

Table structure:

- String keys to identify rows and columns when building the table.
- Colspan and rowspan using column groups and row groups.
- Remaining empty cells filled in automatically, to preserve the structural integrity.
- Warning on cell collisions.

Tag attributes:

- Easily add row classes.
- Easily add row striping classes (odd/even zebra striping and more).
- Easily add column classes that apply to all cells in the column.
- Set html attributes for a row, or for all cells of a column.

API design:

- Method chaining instead of huge arrays of doom.
- Shortcut notations for frequently used stuff.
- Return value and parameter types nicely documented, so your IDE can let you know about possible operations.
- Exceptions thrown for integrity violation.
- Composer and PSR-4.

Basic usage
-----------

[](#basic-usage)

A simple 3x3 table with the diagonal cells filled.

```
$table = \Donquixote\Cellbrush\Table\Table::create()
  ->addRowNames(['row0', 'row1', 'row2'])
  ->addColNames(['col0', 'col1', 'col2'])
  ->td('row0', 'col0', 'Diag 0')
  ->td('row1', 'col1', 'Diag 1')
  ->td('row2', 'col2', 'Diag 2')
;
$html = $table->render();
```

  Diag 0 Diag 1 Diag 2 Too verbose? Look for "Shortcut syntax" below.

Cells in thead and tfoot
------------------------

[](#cells-in-thead-and-tfoot)

A table like above, but with added thead section.

Column names are shared between table sections, but new rows need to be defined for each section.

```
$table = ...
$table->thead()
  ->addRowName('head row')
  ->th('head row', 'col0', 'H0')
  ->th('head row', 'col1', 'H1')
  ->th('head row', 'col2', 'H2')
;
$html = $table->render();
```

  H0H1H2   Diag 0 Diag 1 Diag 2 Additional tbody sections
-------------------------

[](#additional-tbody-sections)

By default, every addRowName() and td() or th() goes into the main tbody section.

So, the following two are equivalent:

```
$table->td('row0', 'col0', 'Cell contents');
$table->tbody()->td('row0', 'col0', 'Cell contents');
```

More named tbody sections can be added like this:

```
$table->tbody('tb1')
  ->addRowName(..)
  ->td(..)
```

Again, the column definitions are shared between table sections, but row definitions need to be added separately.

Full rowspan and colspan
------------------------

[](#full-rowspan-and-colspan)

To let a cell span the entire width of the table, simply set the column name to ''. Likewise, set the row name to '' to span the entire height of the table section.

```
$table->thead()
  ->addRowName('head row')
  ->td('head row', '', 'Horizontal cell in thead.')
;
$table
  ->...
  ->td('', 'col1', 'Vertical cell')
;
```

  Horizontal cell in thead.   \#Vertical cell\# \#\# \#\# Column groups
-------------

[](#column-groups)

Named column groups allow for cells with colspan. In the below example, the column name "products" specifies a colspan cell that spans all 3 products.\* cells, whereas "products.a", "products.b" and "products.c" specifies specific cells without colspan.

```
$table->addColNames(['legend', 'products.a', 'products.b', 'products.c']);
$table->thead()
  ->addRowName('head')
  ->th('head', 'legend', 'Legend')
  // The "Products" label will span 3 columns: products.a, products.b, products.c
  ->th('head', 'products', 'Products')
  ->addRowName('name')
  ->th('name', 'legend', 'Product name')
  ->th('name', 'products.a', 'Product A')
  ->th('name', 'products.b', 'Product B')
  ->th('name', 'products.c', 'Product C')
;
$table
  ->addRowName('width')
  ->th('width', 'legend', 'Width')
  ->td('width', 'products.a', '55 cm')
  ->td('width', 'products.b', '102 cm')
  ..
  ->addRowName('height')
  ..
  ->addRowName('price')
  ->td('price', 'products.a', '7.66 EUR')
```

  LegendProducts Product nameProduct AProduct BProduct C   Width55 cm102 cm7 cm Row groups
----------

[](#row-groups)

Similar to column groups.

```
$table = Table::create()
  ->addColNames(['legend', 'sublegend', 0, 1])
  ->addRowNames(['dimensions.width', 'dimensions.height', 'price'])
  ->th('dimensions', 'legend', 'Dimensions')
  ->th('dimensions.width', 'sublegend', 'Width')
  ->th('dimensions.height', 'sublegend', 'Height')
  ->th('price', 'legend', 'Price')
;
$table->headRow()->thMultiple(['Product 0', 'Product 1']);
$table->rowHandle('dimensions.width')->tdMultiple(['2cm', '5cm']);
$table->rowHandle('dimensions.height')->tdMultiple(['14g', '22g']);
$table->rowHandle('price')->tdMultiple(['7,- EUR', '5,22 EUR']);
```

  Product 0Product 1   DimensionsWidth2cm5cm Height14g22g Price7,- EUR5,22 EUR Combination of row groups and column groups
-------------------------------------------

[](#combination-of-row-groups-and-column-groups)

```
$table = (new Table())
  // Add columns.
  ->addColName('name')
  ->addColNames(['info.color', 'info.price'])
  // Add banana row group.
  ->addRowNames(['banana.description', 'banana.info'])
  ->th('banana', 'name', 'Banana')
  ->td('banana.description', 'info', 'A yellow fruit.')
  ->td('banana.info', 'info.color', 'yellow')
  ->td('banana.info', 'info.price', '60 cent')
;
// Alternative syntax with handles, see "Shortcut syntax" below.
$table->addRow('coconut')->th('name', 'Coconut');
$table->addRow('coconut.description')->td('info', 'Has liquid inside.');
$table->addRow('coconut.info')
  ->td('info.color', 'brown')
  ->td('info.price', '3 dollar')
;
$table->headRow()
  ->th('name', 'Name')
  ->th('info.color', 'Color')
  ->th('info.price', 'Price')
;
```

  NameColorPrice   BananaA yellow fruit. yellow60 cent CoconutHas liquid inside. brown3 dollar Nested groups
-------------

[](#nested-groups)

Groups can have unlimited depth.

```
$table = Table::create()
  ->addRowNames(['T', 'B.T', 'B.B.T', 'B.B.B'])
  ->addColNames(['L', 'R.L', 'R.R.L', 'R.R.R'])
  ->td('T', '', 'top')
  ->td('B', 'L', 'bottom left')
  ->td('B.T', 'R', 'B.T / R')
  ->td('B.B', 'R.L', 'B.B / R.L')
  ->td('B.B.T', 'R.R', 'B.B.T / R.R')
  ->td('B.B.B', 'R.R.L', 'B.B.B / R.R.L')
  ->td('B.B.B', 'R.R.R', 'B.B.B / R.R.R')
;
```

  top bottom leftB.T / R B.B / R.LB.B.T / R.R B.B.B / R.R.LB.B.B / R.R.R Open-ended cells
----------------

[](#open-ended-cells)

Open-end cells allow overlapping colspan cells, like bricks in a wall.

```
$table = (new Table())->addColNames([0, 1, 2, 3, 4, 5, 6, 7]);
$table->addRow(0)->tdMultiple([0, 1, 2, 3, 4, 5, 6, 7]);
$table->addRow(1)
  ->tdOpenEnd(0, '0..2')
  ->tdOpenEnd(3, '3..4')
  ->tdOpenEnd(5, '5')
  ->tdOpenEnd(6, '6..7')
;
$table->addRow(2)
  ->tdOpenEnd(0, '0..1')
  ->tdOpenEnd(2, '2..3')
  ->tdOpenEnd(4, '4..6')
  ->tdOpenEnd(7, '7')
;
```

   0 1 2 3 4 5 6 7   0..2 3..4 5 6..7   0..1 2..3 4..6 7  Shortcut syntax with row handles and column handles
---------------------------------------------------

[](#shortcut-syntax-with-row-handles-and-column-handles)

RowHandle and \*ColHandle allow you to omit one of $rowName and $colName to address a table cell.

```
$table = (new Table())
  ->addRowNames(['row0', 'row1', 'row2'])
  ->addColNames(['legend', 'col0', 'col1', 'col2'])
  ...
;
// Add cells in a "head0" row in the thead section.
$table->headRow()
  ->th('col0', 'Column 0')
  ->th('col1', 'Column 1')
  ->th('col2', 'Column 2')
;
// Add cells in a "legend" column.
$table->colHandle('legend')
  ->th('row0', 'Row 0')
  ->th('row1', 'Row 1')
  ->th('row2', 'Row 2')
;
```

  Column 0Column 1Column 2   Row 0Diag 0 Row 1Diag 1 Row 2Diag 2 Row classes
-----------

[](#row-classes)

Row classes can be added quite easily with `addRowClass()`.

```
$table->addRowClass('row0', 'rowClass0');
```

Row striping
------------

[](#row-striping)

Row striping classes can be added to a table section with `addRowStriping()`.

The default striping is `['odd', 'even']`, but different patterns can be added with three or more stripes.

```
// Odd/even zebra striping.
$table->addRowStriping();
// 3-way striping.
$table->addRowStriping(['1of3', '2of3', '3of3']);
```

The striping always applies to a table section. By default, this wil be the main tbody section.

Column classes
--------------

[](#column-classes)

You can use `addColClass()` to add a class to all cells of a column. This can be done either for all table sections at once, or for specific table sections.

```
$table->addColClass('col0', 'allSectionsColumn0');
$table->tbody()->addColClass('col0', 'tbodyColumn0');
```

Cell classes
------------

[](#cell-classes)

Use `addCellClass()` to add classes to individual cells.

```
$table->addCellClass('row0', 'col0', 'my_class');
```

Column Reordering
-----------------

[](#column-reordering)

Columns can be reordered even after the cells are already added.

```
// Create a table, and render it.
$table = Table::create()
  ->addRowNames(['row0', 'row1', 'row2'])
  ->addColNames(['col0', 'col1', 'col2'])
  ->td('row0', 'col0', 'Diag 0')
  ->td('row1', 'col1', 'Diag 1')
  ->td('row2', 'col2', 'Diag 2')
;
print $table->render();
```

  Diag 0 Diag 1 Diag 2 ```
// Reorder the columns, and render again.
$table->setColOrder(['col1', 'col2', 'col0']);
print $table->render();
```

  Diag 0 Diag 1 Diag 2 More examples?
--------------

[](#more-examples)

You can see more examples in [the unit tests](https://github.com/donquixote/cellbrush/tree/master/tests/src).

Planned features
----------------

[](#planned-features)

Next steps:

- Collision detection.
- Dedicated exception classes.

###  Health Score

47

—

FairBetter than 94% of packages

Maintenance36

Infrequent updates — may be unmaintained

Popularity43

Moderate usage in the ecosystem

Community22

Small or concentrated contributor base

Maturity72

Established project with proven stability

 Bus Factor1

Top contributor holds 96.8% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~364 days

Recently: every ~772 days

Total

11

Last Release

563d ago

Major Versions

v0.0.1 → v1.0.12014-11-14

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/150032?v=4)[Andreas Hennings](/maintainers/donquixote)[@donquixote](https://github.com/donquixote)

---

Top Contributors

[![donquixote](https://avatars.githubusercontent.com/u/150032?v=4)](https://github.com/donquixote "donquixote (122 commits)")[![mcaskill](https://avatars.githubusercontent.com/u/29353?v=4)](https://github.com/mcaskill "mcaskill (2 commits)")[![georgehristov](https://avatars.githubusercontent.com/u/8128250?v=4)](https://github.com/georgehristov "georgehristov (1 commits)")[![makoru-hikage](https://avatars.githubusercontent.com/u/18744785?v=4)](https://github.com/makoru-hikage "makoru-hikage (1 commits)")

---

Tags

colspanhtml-tablephprowspantbodythead

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/donquixote-cellbrush/health.svg)

```
[![Health](https://phpackages.com/badges/donquixote-cellbrush/health.svg)](https://phpackages.com/packages/donquixote-cellbrush)
```

###  Alternatives

[roots/acorn

Framework for Roots WordPress projects built with Laravel components.

9682.1M97](/packages/roots-acorn)[whitecube/nova-flexible-content

Flexible Content &amp; Repeater Fields for Laravel Nova.

8053.0M25](/packages/whitecube-nova-flexible-content)[mopa/bootstrap-bundle

Easy integration of twitters bootstrap into symfony2

7042.9M33](/packages/mopa-bootstrap-bundle)[limenius/react-bundle

Client and Server-side react rendering in a Symfony Bundle

3871.2M](/packages/limenius-react-bundle)[nicmart/string-template

StringTemplate is a very simple string template engine for php. I've written it to have a thing like sprintf, but with named and nested substutions.

2101.7M30](/packages/nicmart-string-template)[symfony/ux-icons

Renders local and remote SVG icons in your Twig templates.

555.8M69](/packages/symfony-ux-icons)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
