The data grid is one of the most commonly used components in modern web applications. Krystal provides a powerful yet lightweight and highly flexible widget that simplifies the creation of feature-rich HTML tables.
You only need to supply your data (usually fetched from a database) and configure how each column should be rendered β for example, whether it supports inline editing, filtering, sorting, or custom formatting.
The GridViewWidget provides a rich set of built-in features to enhance your data tables:
<select> dropdowns for predefined options.These features can be enabled individually or combined, giving you full control over the grid's behavior while keeping the implementation lightweight and intuitive.
The core requirement for using GridViewWidget is to provide two things:
Data Source Format
The data source must be a multidimensional array where:
Example:
$rows = [
[
'name' => 'Dave',
'age' => 27,
'email' => 'dave@example.com'
],
[
'name' => 'Julia',
'age' => 30,
'email' => 'julia@example.com'
],
[
'name' => 'Josh',
'age' => 20,
'email' => 'josh@example.com'
]
];
This format aligns perfectly with common data retrieval methods, such as:
Rows should generally share the same keys for consistent rendering, though extra fields not used in columns are ignored and can be useful for custom logic.
To display the data grid in your view template, use the following minimal code:
<?php
use Krystal\Widget\GridView\GridViewWidget;
?>
<div class="table-responsive">
<?= $this->widget(new GridViewWidget($rows, [
'tableClass' => 'table table-hover table-bordered table-striped table-condensed',
'columns' => [
[
'column' => 'name'
],
[
'column' => 'age'
],
[
'column' => 'email'
]
]
])); ?>
</div>
This is the minimal configuration required to render a functional, styled table. The widget will automatically:
<thead> with column headers (capitalized key names by default).<tbody>.You can further customize headers, add sorting, filters, actions, or custom rendering β see the Column Configuration section for details.
The key tableClass defines a class that generated table will have. If you omit this, then the default CSS class table table-hover table-bordered table-striped will be used.
The key columns expects a collection of arrays. Each array represents a column to be rendered in a table.
You can add custom HTML attributes to individual table cells on a per-column basis. This is useful for applying specific classes, data attributes, styles, or other properties to all cells in a column.
Use the attributes option and provide an associative array of attribute names and values.
[
'column' => 'name',
'attributes' => [
'class' => 'text-start'
]
],
Notes
<td>) only, not header cells (<th>)By default, when rendering a table column, the component automatically generates the header label by normalizing the column name (e.g., converting underscores to spaces and capitalizing words).
For a column defined as:
[
'column' => 'name'
]
The header will be rendered as Name.
For a column like first_name, it would become First name.
To specify a custom header label, add the label key to the column configuration:
[
'column' => 'name',
'label' => 'Customer name'
]
This will render the column header as Customer name.
Additional notes
Your data source may contain numeric codes, constants, or raw values (e.g., -1, 0, 1) that are not user-friendly. To display more meaningful text in table cells, you can transform these values using the value option.
The value option accepts a callback function that receives the current row data and returns the desired display value.
Basic usage
[
'column' => 'status',
'value' => function($row){
if ($row['status'] == -1) {
return 'Canceled';
}
if ($row['status'] == 0){
return 'Success';
}
}
]
How it works
$row β an array representing the current row being rendered.By default, all defined columns are visible in the rendered table. To hide a specific column, set the optional hidden parameter to true in its configuration.
Basic usage
[
'column' => 'name',
'hidden' => true,
'label' => 'Customer name'
]
This will exclude the name column from being rendered.
Batch operations (e.g., delete multiple rows, change status in bulk) are supported via checkboxes that appear in the first column of the table.
To enable this feature:
pk option (this column must exist in your data rows).<form> tag so selected items can be submitted.Basic usage
<?php
use Krystal\Widget\GridView\GridViewWidget;
?>
<form action="....">
<div class="table-responsive">
<?= $this->widget(new GridViewWidget($rows, [
'batch' => true,
'pk' => 'id', // Must be in source array
'columns' => [
// ...
]
])); ?>
</div>
<button type="submit">Apply batch</submit>
</form>
How it works
'pk' => 'id').$_POST['batch'] (or $_GET['batch'] if using GET).The widget supports per-column filtering and sorting directly in the table header. By default, columns have sorting enabled (with clickable header links) when rendering a filter.
Enabling basic text filtering
To add a filter input to a column, set the filter option to true. This renders a text input field by default.
[
'column' => 'name',
'label' => 'Product Name',
'filter' => true
]
Dropdown (select) filtering
For a dropdown filter instead of a text input, provide an associative array of value-label pairs to the filter option and set 'type' => 'select'.
[
'column' => 'color',
'label' => 'Product Color',
'type' => 'select',
'filter' => [
'r' => 'Red',
'b' => 'Blue',
'y' => 'Yellow'
]
]
Notes
Disabling sorting
Sorting is enabled by default for all columns (header becomes clickable with asc/desc indicators). To disable sorting on a specific column, explicitly set sorting to false.
[
'column' => 'name',
'label' => 'Product Name',
'filter' => true,
'sorting' => false // Prevents sorting on this column
]
Important notes
filter[name]=foo&sort=price&order=desc).The widget supports inline editing, allowing users to quickly update individual cell values directly in the table without opening a separate edit form.
To make a column editable, set the editable option to true. This renders the cell content inside a text input field (by default).
[
'column' => 'name',
'label' => 'Customer Name',
'editable' => true
]
Handling submitted data
On form submission, edited values are sent in the $_POST['editable'] array, structured as:
$_POST['editable'] = [
'column_name' => [
'row_primary_key' => 'new_value',
// ...
]
];
Example of received data:
$_POST['editable'] = [
'name' => [
'5' => 'John Doe',
'7' => 'Jane Smith',
],
'email' => [
'5' => 'john@example.com',
],
];
This means:
Sometimes you need to output a column that doesn't exist as a direct key in your source array. You can create it just like any regular column, but compute its value dynamically using a closure that accesses nested data, performs calculations, or fetches external values.
This is perfect for:
Here is a sample dataset representing users with their basic info and nested order details:
$users = [
[
'name' => 'Jane Doe',
'email' => 'jane@example.com',
'order' => [
'status' => [
'total' => '1842',
'currency' => 'USD',
'qty' => 2
]
]
],
[
'name' => 'John Smith',
'email' => 'john@example.com',
'order' => [
'status' => [
'total' => '950',
'currency' => 'EUR',
'qty' => 1
]
]
]
];
Define a column name and a value closure that receives the current $row and returns the cell content as a string.
<?php
use Krystal\Widget\GridView\GridViewWidget;
?>
<div class="table-responsive">
<?= $this->widget(new GridViewWidget($rows, [
'tableClass' => 'table table-hover table-bordered table-striped table-condensed',
'columns' => [
[
'column' => 'name',
'label' => 'Full name' // Optional: custom header
],
[
'column' => 'email'
],
[
'column' => 'details', // This is virtual column (no source key)
'label' => 'Order Summary',
'value' => function($row) {
// Access nested data safely
$status = $row['order']['status'] ?? [];
$total = $status['total'] ?? '0';
$currency = $status['currency'] ?? 'N/A';
$qty = $status['qty'] ?? 0;
return sprintf(
'<strong>Total: %s %s</strong><br><small>QTY: %d</small>',
number_format($total), // Auto-format numbers
$currency,
$qty
);
}
],
]
])); ?>
</div>
The rowAttributes option allows you to dynamically apply HTML attributes to individual table rows (<tr> elements). This is particularly useful for highlighting rows based on data conditions (e.g., status, priority, expiration).
The rowAttributes key accepts an array where:
$row and returns a string value.Dynamic row class example (Conditional highlighting)
Highlight rows with status == 0 using Bootstrap's bg-danger class:
<?php
use Krystal\Widget\GridView\GridViewWidget;
?>
<form action="....">
<div class="table-responsive">
<?= $this->widget(new GridViewWidget($rows, [
'rowAttributes' => [
'class' => function ($row) {
// Red background for failed/inactive
if ($row['status'] == 0) {
return 'bg-danger text-white';
}
// Green for success/active
if ($row['status'] == 1) {
return 'bg-success text-white';
}
return ''; // No additional class
},
// Example: add static attributes to all rows
'data-type' => 'order-row'
],
'columns' => [
// ...
]
])); ?>
</div>
<button type="submit">Apply</submit>
</form>
The actions option allows you to add custom action links or buttons to each table row. These are typically rendered in a dedicated "Actions" column at the end of the table (e.g., Edit, View, Delete buttons).
The actions key accepts an array of callback functions. Each callback receives the current $row array as its parameter and must return a string (usually HTML for a link or button).
Basic usage
<?php
use Krystal\Widget\GridView\GridViewWidget;
?>
<div class="table-responsive">
<?= $this->widget(new GridViewWidget($rows, [
'tableClass' => 'table table-hover table-bordered table-striped table-condensed',
'columns' => [
[
'column' => 'name'
],
// ....
],
'actions' => [
function($row){
// Here could be a hyperlink
return 'Delete #' . $row['id']
},
function($row){
// Here could be a hyperlink
return 'Edit #' . $row['id']
},
]
])); ?>
</div>
Notes