Utility functions
Classnames
Conditionally join classnames together.
Function takes any number of arguments and returns a string containing space-separated class names.
Example usage
/**
* @param mixed ...$args Arguments.
* @return string Classnames
*/
use function T2\Utils\classnames;
$class_names = 'foo bar baz';
$class_names = classnames( 't2-block', $class_names );
// Output: t2-block foo bar baz
$color = 'red';
$size = 'large';
$is_active = true;
$is_disabled = false;
$class_names = classnames(
'button',
[
'button--color' => $color,
'button--size' => $size,
'button--active' => $is_active,
'button--disabled' => $is_disabled,
],
'button--icon',
[
'icon',
'icon--close',
[
'icon__inner',
'icon__inner--small' => $size === 'small',
],
]
);
echo $class_names;
// Output: button button--color-red button--size-large button--active button--icon icon icon--close icon__inner icon__inner--small
ID Attribute
Creates a valid id attribute based on a string.
Example usage
/**
* @param string $string String to create id from.
*/
use function T2\Utils\id_attribute;
function render( array $attributes, string $content ): string {
$args = [
'title' => $attributes['title'],
];
$button_id = $args['id'] ?? id_attribute( $args['title'] );
$panel_id = id_attribute( 'accordion-panel' );
// Rest of the code...
}
Array to attrs
Build attribute strings from associative array.
This function takes an array of HTML attributes and their values, sanitizes them to prevent malicious input, and returns a string of properly formatted HTML attribute-value pairs.
Example usage
/**
* @param array $attributes Attributes.
*/
use function T2\Utils\array_to_attrs;
$my_array = [
'aria-label' => 'my-label',
'class' => 'custom-class',
'data-foo' => 'bar',
'data-baz' => 'qux',
'disabled' => true,
];
echo array_to_attrs($attributes);
// Output: aria-label="my-label" class="custom-class" data-foo="bar" data-baz="qux" disabled
// Omit false values by setting second argument to true. (E.g. disabled="false" would give an unexpected result)
echo array_to_attrs( [
'type' => 'button',
'class' => 'wp-element-button',
'disabled' => false,
'data-id' => 1,
], true );
// Output: type="button" class="wp-element-button" data-id="1"
Array to style
Build a style
attribute string from an associative array.
This function takes an array of CSS properties and their values, and returns a properly formatted CSS string.
The returned CSS string is escaped with esc_attr()
by default, but this can be disabled.
Example usage
/**
* @param array $styles Css styles.
*/
use function T2\Utils\array_to_style;
$styles = [
'color' => '#fff',
'padding' => (string) 0,
'margin' => '2rem',
];
echo array_to_style( $styles );
// Output: color: #fff; padding: 0; margin: 2rem;
Array merge values
The function takes any number of arrays as input parameters and returns a new array that contains only the unique values from all the input arrays.
Example usage
/**
* @param array ...$arrays Arrays to merge.
*/
use function T2\Utils\array_merge_values;
$array1 = [ "apple", "banana", "orange", "peach" ];
$array2 = [ "orange", "peach", "grapefruit", "mango" ];
$array3 = [ "mango", "papaya", "banana", "kiwi" ];
$result = array_merge_values( $array1, $array2, $array3 );
print_r($result);
// Output: Array ( [0] => apple [1] => banana [2] => orange [3] => peach [4] => grapefruit [5] => mango [6] => papaya [7] => kiwi )
String to array
The function takes a string and returns an array with non-empty and unique entries.
The string separator is a comma by default, but can be any character.
Example usage
/**
* @param mixed $value A string to be converted.
* @param string $separator The string separator.
*/
use function T2\Utils\string_to_array;
print_r( string_to_array( 'left, center, right' ) );
// Output: Array ( [0] => left [1] => center [2] => right )
print_r( string_to_array( 'left, center, right, left, ' ) );
// Output: Array ( [0] => left [1] => center [2] => right )
print_r( string_to_array( 'left|center|right', '|' ) );
// Output: Array ( [0] => left [1] => center [2] => right )
Settings Page helper class
Creating settings pages is boring, tedious and repetitive work, which is probably the main reason why developers tend to skip them. Too bad, because they are quite useful and allow users to customize the behavior of a plugin or theme without touching any code.
Example settings page, see Full example usage below.

ACF is one way to go, but it comes with a lot of overhead for just a simple settings page. With the lightweight and single-purpose T2\Utils\Settings_Page
helper class, creating WordPress admin settings pages is a breeze. They are not developed, but configured.
Minimum example usage
add_action( 'init', function (): void {
// Register a new settings page.
$page = \T2\Utils\Settings_Page::register_page( 'your-slug', [
'menu_title' => __( 'Your menu title', 'your-text-domain' ),
'page_title' => __( 'Your settings page title', 'your-text-domain' ),
'plugin_basename' => plugin_basename( __FILE__ ), // Adds a "Settings" link to the plugin on the Plugins page.
] );
// Add one or more sections to the settings page.
$page->add_section( [
'section' => 'your_prefix_section_01',
'label' => __( 'Add a section with add_section()', 'your-text-domain' ),
'description' => __( 'This is a section description.', 'your-text-domain' ),
] );
// Add one or more fields (numbers, text, checkboxes, dropdowns, images, etc.) to each section.
$page->add_number( [
'option' => 'your_prefix_option_01',
'section' => 'your_prefix_section_01',
'label' => __( 'add_number()', 'your-text-domain' ),
'default' => 123,
'description' => __( 'This is a number field.', 'your-text-domain' ),
] );
} );
How it works
First, you register a new settings page (Settings_Page::register_page()
) with one or more sections (add_section()
). Then you add one or more fields to each section (add_*()
). Each field represents a WordPress option that contains the value of the field.
Get the values of the options as usual with get_option( 'your_option' )
anywhere in your code. If no option value has been stored yet, the specified default value is returned.
Important: For T2 v8.19 and higher, call the register_page()
and add_*()
methods during the init
action or earlier in order to register the settings (options) with their default values before retrieving them with get_option()
. And don't wrap the Settings_Page
functions in an is_admin()
condition. This will prevent the default values from being available in frontend and REST API code. Older versions should still use the admin_menu
hook.
Settings_Page::register_page() arguments
The register_page( string $slug, array $args )
method takes two arguments: a unique slug for the settings page and an array of arguments. The following arguments are supported:
menu_title
(string, required): The text to be displayed in the admin menu.page_title
(string, required): The text to be displayed at the top of the settings page.parent_slug
(string, optional): The slug of the parent menu, defaults to the WordPress "Settings" menu.capability
(string, optional): The capability required for the settings page, defaults tomanage_options
.load_page_callback
(callable, optional): Callback for adding custom assets (styles, scripts) to the settings page.show_page_callback
(callable, optional): Callback to display the settings on a custom page.position
(int, optional): The position (priority) of the menu item in the parent menu.plugin_basename
(string, optional): Provide the plugin basename to display a "Settings" link on the "Plugins" page.
Settings_Page::add_section( array $args ) arguments
The add_section( array $args )
method takes an array of arguments. The following arguments are supported:
section
(string, required): A globally unique identifier for the section (use a prefix).label
(string, required): The label (title) of the section.description
(string|string[], optional): A section description, displayed below the section title.
Settings_Page::add_*( array $args ) arguments
The add_*()
methods all have the same signature with minor variations depending on the field type. Each method take an array of arguments. The following arguments are supported:
option
(string, required): A globally unique identifier for the setting/option/field (use a prefix).section
(string, required): The identifier of the section the field belongs to.label
(string, required): The label (title) of the field.default
(mixed, optional): The default value of the option.description
(string|string[], optional): A field description.placeholder
(string, optional): A placeholder for text input fields.show_in_rest
(bool, optional): Whether to expose this option in the REST API, defaults tofalse
.sanitize
(callable, optional): A custom sanitization callback for the option value (replaces default sanitization).class
(string, optional): Additional CSS class names for the fieldinput
element.attributes
(array, optional): Additional HTML attributes for the fieldinput
element,
For add_radio()
, add_multibox()
and add_dropdown()
fields:
terms
(array, required): An array of available options. Can be an associative array ofvalue => label
pairs or a simple array ofvalues
.
The option value type (both the default
and the stored value) depends on the field type:
add_number()
: integer or floatadd_text()
,add_textarea()
,add_password()
: stringadd_radio()
,add_dropdown()
: string (the value of the selected option)add_checkbox()
: 1 or 0 (nottrue
orfalse
).add_multibox()
,add_list()
: array of selected values or list items.add_image()
: integer (attachment ID).
The following WordPress admin class names are used by default, but can be overridden with the class
argument:
small-text
: Small input field (default for number fields).regular-text
: Regular input field (default for text fields, password fields and dropdowns).large-text
: Wide input field (default for textarea fields).
Available field methods
add_number()
: Add a number input field.add_text()
: Add a text input field.add_textarea()
: Add a textarea field.add_password()
: Add a password input field.add_radio()
: Add a set of radio buttons.add_checkbox()
: Add a single checkbox.add_multibox()
: Add a set of checkboxes.add_dropdown()
: Add a select dropdown field.add_list()
: Add a list field (add, edit, remove items).add_image()
: Add an image selector, i.e. for placeholder images (stores attachment ID).
Full example usage
<?php
/**
* The `register_page()`and `add_*` methods of its returned instance should be
* called during the `init` action at the latest in order to register the
* settings (options) with their default values BEFORE they are used with
* `get_option()` elsewhere in your code.
*/
add_action( 'init', function (): void {
$page = \T2\Utils\Settings_Page::register_page( 'your-slug', [
'menu_title' => __( 'Your menu title', 'your-text-domain' ),
'page_title' => __( 'Your settings page title', 'your-text-domain' ),
'parent_slug' => 'options-general.php', // This is the default value.
'capability' => 'manage_options', // This is the default value.
'plugin_basename' => plugin_basename( __FILE__ ), // Adds a plugin action link to the settings page.
] );
$page->add_section( [
'section' => 'your_prefix_section_01',
'label' => __( 'Add a section with add_section()', 'your-text-domain' ),
'description' => __( 'This is a section description.', 'your-text-domain' ),
] );
$page->add_number( [
'option' => 'your_prefix_option_01',
'section' => 'your_prefix_section_01',
'label' => __( 'add_number()', 'your-text-domain' ),
'default' => 123,
'description' => __( 'This is a number field.', 'your-text-domain' ),
] );
$page->add_text( [
'option' => 'your_prefix_option_02',
'section' => 'your_prefix_section_01',
'label' => __( 'add_text()', 'your-text-domain' ),
'default' => __( 'The default value', 'your-text-domain' ),
'placeholder' => __( 'The placeholder', 'your-text-domain' ),
'description' => __( 'This is a text field.', 'your-text-domain' ),
] );
$page->add_text( [
'option' => 'your_prefix_option_02a',
'section' => 'your_prefix_section_01',
'label' => __( 'add_text(), wide' , 'your-text-domain' ),
'default' => __( 'The default value', 'your-text-domain' ),
'placeholder' => __( 'The placeholder', 'your-text-domain' ),
'description' => __( "This is a wide text field with 'class' => 'large-text'.", 'your-text-domain' ),
'class' => 'large-text',
] );
$page->add_textarea( [
'option' => 'your_prefix_option_02b',
'section' => 'your_prefix_section_01',
'label' => __( 'add_textarea()', 'your-text-domain' ),
'default' => __( 'The default value', 'your-text-domain' ),
'description' => __( 'This is a text field.', 'your-text-domain' ),
] );
$page->add_password( [
'option' => 'your_prefix_option_03',
'section' => 'your_prefix_section_01',
'label' => __( 'add_password()', 'your-text-domain' ),
'default' => 'something',
'description' => __( 'This is a password field.', 'your-text-domain' ),
] );
$page->add_radio( [
'option' => 'your_prefix_option_04',
'section' => 'your_prefix_section_01',
'terms' => [ 'hello' => 'Hello', 'world' => 'World' ],
'default' => 'world',
'label' => __( 'add_radio()', 'your-text-domain' ),
'description' => __( 'These are radio buttons.', 'your-text-domain' ),
] );
$page->add_checkbox( [
'option' => 'your_prefix_option_05',
'section' => 'your_prefix_section_01',
'default' => 1,
'label' => __( 'add_checkbox()', 'your-text-domain' ),
'description' => __( 'Values are stored as 1 or 0 (not true or false)', 'your-text-domain' ),
'attributes' => [ 'readonly' => false, 'disabled' => false ],
] );
$page->add_multibox( [
'option' => 'your_prefix_option_06',
'section' => 'your_prefix_section_01',
'terms' => [ 'option1' => 'Option 1', 'option2' => 'Option 2', 'option3' => 'Option 3' ],
'default' => [ 'option1', 'option3' ],
'label' => __( 'add_multibox()', 'your-text-domain' ),
'description' => [
__( 'Select none, one or multiple options.', 'your-text-domain' ),
__( 'The selected values are stored in an array.', 'your-text-domain' ),
],
] );
$page->add_dropdown( [
'option' => 'your_prefix_option_07',
'section' => 'your_prefix_section_01',
'terms' => [ 'option1' => 'Option 1', 'option2' => 'Option 2', 'option3' => 'Option 3' ],
'default' => 'option2',
'label' => __( 'add_dropdown()', 'your-text-domain' ),
'description' => __( 'This is a select field.', 'your-text-domain' ),
] );
$page->add_list( [
'option' => 'your_prefix_option_08',
'section' => 'your_prefix_section_01',
'default' => [ 'List item 1', 'List item 2', 'List item 3', 'List item 4', 'List item 5' ],
'label' => __( 'add_list()', 'your-text-domain' ),
'description' => [
__( 'This fields lets you add, edit and remove list items, i.e. urls.', 'your-text-domain' ),
__( 'The list items are stored in an array.', 'your-text-domain' ),
]
] );
$page->add_image( [
'option' => 'your_prefix_option_09',
'section' => 'your_prefix_section_01',
'default' => 17156, // Attachment ID.
'label' => __( 'add_image()', 'your-text-domain' ),
'description' => [
__( 'This is an image field.', 'your-text-domain' ),
__( 'The attachment id of the selected image is stored as an integer.', 'your-text-domain' ),
],
] );
} );
Advance usage
The content (sections and fields) of a setting page can be added over multiple files and even multiple plugins or themes.
After you have registered a settings page with Settings_Page::register_page( 'your-slug', $args )
, you can retrieve the instance of the page with Settings_Page::get_page( 'your-slug' )
and add more sections and fields to it. If you call register_page()
again with the same slug, it will also return the existing instance instead of creating a new one.
With this approach, you can define sections and fields where they are used in get_option()
, instead of maintaining one central settings file. Even if the fields are added in different places, they will all appear on the same settings page. This way, you can add and remove fields without touching a central settings file as your project grows or changes.
Under the hood (if you really want to know)
So, why use the init
hook (or earlier) instead of admin_menu
for a settings page? All the add_<field>()
methods do two related but very different things. They register the settings with their default values (and other stuff) and they render the corresponding fields on settings pages.
Unfortunately, they can't do both at the same time or in the same context. The settings should be registered early and globally (admin + frontend + REST) before they are used with get_option()
, but the fields should only be rendered on the actual settings page in the admin area.
To solve this dilemma, Settings_Page
methods register the settings immediately, but internally check is_admin()
and wait for admin_menu
to render the fields on the settings page. So, for most request, only the first part (registering the settings) is ever executed, while the second part (rendering the fields) only happens in the admin area.
Custom media
Package also provides default T2 aliases as custom media queries (src/custom-media.css
) based on the most used Gutenberg breakpoints. This package uses custom media queries PostCSS plugin and expects your setup to support it.
Name | Pixel Width | Alias |
---|---|---|
mobile /tiny |
480 | --t2-tiny-and-up |
small |
600 | --t2-small-and-up |
medium |
782 | --t2-medium-and-up |
large |
960 | --t2-large-and-up |
xlarge |
1080 | --t2-xlarge-and-up |
wide |
1280 | --t2-wide-and-up |
huge |
1440 | --t2-huge-and-up |
xhuge |
1920 | --t2-xhuge-and-up |
Example usage
Use custom media alias in your css file
.some-element {
color: blue;
@media (--t2-medium-and-up) {
color: red;
}
}