The Columns field type arranges child fields in a responsive multi-column grid layout. Like the Wrapper field type, it is a purely visual container — its children store their values flat at the parent level. The key difference is that Columns uses CSS Grid to place fields side by side instead of stacking them vertically.
The layout is responsive: columns automatically reduce when they would be narrower than 300px, ensuring fields remain usable on smaller screens.
array(
'type' => 'columns',
'columns' => 3,
'items' => array(
'first_name' => array(
'type' => 'text',
'label' => 'First Name',
),
'last_name' => array(
'type' => 'text',
'label' => 'Last Name',
),
'email' => array(
'type' => 'email',
'label' => 'Email Address',
),
),
)For Default Field Properties, see Field Types Definition.
items(array) — An array of field definitions that make up the columns' content. Each item is a complete field definition with its own type, label, and other properties.columns(integer|array) — The number of columns in the grid layout, or an array of CSS column widths (e.g.array( '1fr', '50px', '2fr' )). When an array, the number of columns is the array length and each entry defines that column's width. When an integer, all columns are equal width (1fr). Defaults to2. This is the maximum number of columns — the actual number may be lower on narrow containers (see Responsive Behavior).gap(string) — A CSS gap value to override the default spacing between columns and rows. For example,'16px','1rem', or'8px 16px'(row gap / column gap).class_name(string) — A CSS class name added to the columns container element. This is applied alongside the defaultwpifycf-field-columnsclass.
These properties can be set on individual field definitions inside items to control their placement within the grid:
column(integer) — A 1-based column index specifying which column the field should start in. When omitted, the field is auto-placed by CSS Grid.column_span(integer) — The number of columns the field should span. Defaults to1. Useful for fields that need more horizontal space, such as textareas or WYSIWYG editors.
column |
column_span |
Behavior |
|---|---|---|
| Set | Not set | Placed at that column, spans 1 |
| Not set | Set | Auto-placed, spans N columns |
| Set | Set | Placed at column X, spans N columns |
| Not set | Not set | Auto-placed, spans 1 (default) |
When the container is too narrow and columns collapse (see Responsive Behavior), all explicit column placements are ignored and CSS Grid auto-flow handles the layout.
The Columns field automatically adapts to the available container width:
- Each column has a minimum width of 300px
- If the container is too narrow for the requested number of columns, columns are reduced:
effectiveColumns = floor(containerWidth / 300) - The minimum is always 1 column
- When columns collapse below the requested count, all explicit
columnplacements are ignored — fields flow naturally in row order - When using an array of custom widths and columns collapse, the custom widths are replaced with equal-width (
1fr) columns
This means a columns: 4 layout on a 900px-wide container will display as 3 columns, and on a 500px container as 1 column.
This field does not store its own value. Children store their values flat at the parent level.
This behavior is identical to the Wrapper field type.
'name_columns' => array(
'type' => 'columns',
'columns' => 2,
'items' => array(
'first_name' => array(
'type' => 'text',
'label' => 'First Name',
'required' => true,
),
'last_name' => array(
'type' => 'text',
'label' => 'Last Name',
'required' => true,
),
),
)Use column_span to make a field take up more horizontal space:
'contact_columns' => array(
'type' => 'columns',
'columns' => 3,
'items' => array(
'first_name' => array(
'type' => 'text',
'label' => 'First Name',
'column' => 1,
),
'last_name' => array(
'type' => 'text',
'label' => 'Last Name',
'column' => 2,
),
'email' => array(
'type' => 'email',
'label' => 'Email',
'column' => 3,
),
'bio' => array(
'type' => 'textarea',
'label' => 'Bio',
'column_span' => 3,
),
),
)The bio field spans all 3 columns, creating a full-width row beneath the three single-column fields.
When a columns field is placed inside a group, its children's values stay flat within the group's namespace:
'profile' => array(
'type' => 'group',
'label' => 'Profile',
'items' => array(
'avatar' => array(
'type' => 'attachment',
'label' => 'Avatar',
),
'details_columns' => array(
'type' => 'columns',
'columns' => 2,
'items' => array(
'bio' => array(
'type' => 'textarea',
'label' => 'Bio',
),
'website' => array(
'type' => 'url',
'label' => 'Website',
),
),
),
),
)The stored value looks like this — bio and website sit alongside avatar inside the group:
array(
'profile' => array(
'avatar' => 123,
'bio' => 'A short bio...',
'website' => 'https://example.com',
),
)Use a columns field to show or hide a block of side-by-side fields together:
'show_social' => array(
'type' => 'toggle',
'label' => 'Show Social Links',
'title' => 'Display social media links',
),
'social_columns' => array(
'type' => 'columns',
'columns' => 3,
'conditions' => array(
array( 'field' => 'show_social', 'value' => true ),
),
'items' => array(
'twitter' => array(
'type' => 'url',
'label' => 'Twitter URL',
),
'facebook' => array(
'type' => 'url',
'label' => 'Facebook URL',
),
'linkedin' => array(
'type' => 'url',
'label' => 'LinkedIn URL',
),
),
)Use an array to define individual column widths instead of equal-width columns:
'sidebar_layout' => array(
'type' => 'columns',
'columns' => array( '1fr', '50px', '2fr' ),
'items' => array(
'sidebar' => array(
'type' => 'textarea',
'label' => 'Sidebar Content',
),
'divider' => array(
'type' => 'text',
'label' => 'Divider',
),
'main' => array(
'type' => 'textarea',
'label' => 'Main Content',
),
),
)The three columns will be sized 1fr, 50px, and 2fr respectively. When the container is too narrow, the layout collapses to fewer equal-width columns.
Override the default gap between columns:
'settings_columns' => array(
'type' => 'columns',
'columns' => 2,
'gap' => '8px 24px',
'items' => array(
'option_a' => array(
'type' => 'toggle',
'label' => 'Option A',
'title' => 'Enable option A',
),
'option_b' => array(
'type' => 'toggle',
'label' => 'Option B',
'title' => 'Enable option B',
),
),
)$f = new \Wpify\CustomFields\FieldFactory();
// Equal-width columns
$f->columns(
columns: 3,
items: array(
$f->text( label: 'First Name' ),
$f->text( label: 'Last Name' ),
$f->email( label: 'Email' ),
$f->textarea( label: 'Bio', column_span: 3 ),
),
);
// Custom column widths
$f->columns(
columns: array( '200px', '1fr', '1fr' ),
items: array(
$f->attachment( label: 'Avatar' ),
$f->text( label: 'First Name' ),
$f->text( label: 'Last Name' ),
),
);- The key difference from the Wrapper field type is the CSS Grid layout — Wrapper stacks fields vertically, while Columns arranges them side by side.
- Like Wrapper, the Columns field does not nest values. Children store their values flat at the parent level.
- By default, the columns field sets
renderOptionstonoLabel: true,noFieldWrapper: true, andnoControlWrapper: true, so it renders with no label or extra wrapping markup. - Each column item has
container-type: inline-sizeset, so child fields' responsive label/control layout switching uses the column width rather than the full container width. - Columns children participate in validation at the parent level. The validation system flattens columns items using
flattenWrapperItems()so each child is validated individually. - Columns can be nested inside groups, wrappers, or other columns.
- In PHP, the
flatten_items()method hoists columns children to the parent level for meta registration and sanitization, just like wrapper fields. - Conditionally hidden fields inside columns are automatically collapsed (no empty grid cells) via CSS
:has()selector.