Utility functions

link Classnames

Conditionally join classnames together.
Function takes any number of arguments and returns a string containing space-separated class names.

link 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

link ID Attribute

Creates a valid id attribute based on a string.

link 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...
}

link 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.

link 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"

link 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.

link 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;

link 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.

link 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 )

link 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.

link 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 )

link 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.

link 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.

link 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' ),
	] );
} );

link 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.

link 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:

link Settings_Page::add_section( array $args ) arguments

The add_section( array $args ) method takes an array of arguments. The following arguments are supported:

link 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:

For add_radio(), add_multibox() and add_dropdown() fields:

The option value type (both the default and the stored value) depends on the field type:

The following WordPress admin class names are used by default, but can be overridden with the class argument:

link Available field methods

link 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' ),
		],
	] );
} );

link 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.

link 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.

link 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

link Example usage

Use custom media alias in your css file

.some-element {
  color: blue;

  @media (--t2-medium-and-up) {
    color: red;
  }
}