Extended WordPress Settings API Tutorial, Part 2

Download the files (updated June 5, 2011)

This post is based on part 1 of this tutorial, so read it if you haven’t.

We left off with a theme options class, My_Theme_Options, that had all the behind-the-scenes work complete. The next step is the HTML.

4. Display the Options Page and Sections

Up to this point, we’ve had a completely blank options page in our admin. We want to see the fruits of our labor! We use three functions to make this happen: display_page(), display_section(), and display_setting(). Let’s tackle the page and sections first.

Options Page HTML

Add this to the display_page() function:

echo '<div class="wrap">
<div class="icon32" id="icon-options-general"></div>
<h2>' . __( 'Theme Options' ) . '</h2>
<form action="options.php" method="post">
	settings_fields( 'mytheme_options' );
	do_settings_sections( $_GET['page'] );

	echo '<p class="submit"><input name="Submit" type="submit" class="button-primary" value="' . __( 'Save Changes' ) . '" /></p>

Look at the theme options page now. There is a big “Theme Options” heading, a subheading for each section, and a whole lot of errors. That’s because there’s no display_section() function written yet, and we’ll get to that next.

The “icon32” div at the top of the above code is what creates the nice-looking settings icon next to our heading. You can change the id to use a different WordPress icon (onextrapixel has a great cheat sheet).

The settings_fields() function inserts a few hidden form fields, including a nonce for security.

The do_settings_sections() function is looking for sections assigned to the current page slug. Remember the code we used to register the sections in part 1:

add_settings_section( $slug, $title, array( &$this, 'display_section' ), 'mytheme-options' );

The page slug was referenced there, along with the display_section() function.

Sections HTML

The purpose of the display_section() function is only to add descriptive text (or other HTML) to that specific settings section. Whatever is output by this function will be displayed below the section subheading. If you register the sections individually instead of in a loop, you can use a different callback function for each section and insert custom content here.

For now, just put an empty function inside the class:

/* Description for section */
public function display_section() {
	// code

5. Display the Form Fields

The reason we wrote a helper function to add our settings to the database is so we could pass certain arguments along to the callback function (which is responsible for outputting the HTML to display the field). This allows us to use the same callback function for every single field, and still get unique displays for each type. Let’s walk through the callback function, display_setting().

/* HTML output for individual settings */
public function display_setting( $args = array() ) {

	extract( $args );

	$options = get_option( 'mytheme_options' );

	if ( ! isset( $options[$id] ) && $type != 'checkbox' )
		$options[$id] = $std;
	elseif ( ! isset( $options[$id] ) )
		$options[$id] = 0;

	$field_class = '';
	if ( $class != '' )
		$field_class = ' class="' . $class . '"';

The arguments for this function, which are the same arguments as our create_setting() function, get passed in an array and then turned into variables by extract(). Then, we create an array of all the existing options in the database with get_option(). The argument for that, ‘mytheme_options’, is the name of our master options array.

If the current setting, $id, doesn’t exist in the database, then it assigns that setting its default value. You should also exclude checkboxes in this if statement, because checkboxes don’t have user-created values (they’re either on or off).

Then we check for a custom class to use for this field. If it’s equal to anything other than an empty string, it will output ‘class=”$class”‘ in the HTML form field.

There is a fairly massive swtich statement coming up next. I’ll just give you the text field code for now, and the rest after I explain it:

	switch ( $type ) {

		case 'text':
			echo '<input class="regular-text' . $field_class . '" type="text" id="' . $id . '" name="mytheme_options[' . $id . ']" placeholder="' . $std . '" value="' . esc_attr( $options[$id] ) . '" />';

	 		if ( $desc != '' )
	 			echo '<br /><span class="description">' . $desc . '</span>';


This is pretty straightforward. It’s printing an HTML input field, with a custom class if the setting has one, and setting its name, id, and value. It prints the description, if it exists, on a line below the field in small text. It’s also setting a “placeholder” attribute. This is an HTML5 attribute, and we’ll be using it to find the default value for that field in some jQuery later on.

Note that I’m using the text field as the default in this switch statement. You can use whichever type you want as the default, just make sure it comes last in the switch statement and has “default:” under the “case” line.

Where are the form labels and the table structure?

The Settings API prints the HTML for these settings inside a table cell that’s already created. WordPress automatically creates the table, creates a row for each field with a table header containing the field label, and opens and closes the table cell around the form field.

This is both good and bad. It saves us a lot of code doing that manually, and it formats it like other settings pages in WordPress so it has the same look and feel. However, it is less flexible for those who want their options page to look a little different. This is where the jQuery comes in (back to that in a bit).

Below is the rest of the switch statement. This code should all go after switch ( $type ) { and before case 'text': for it to work properly.

case 'heading':
	echo '</td></tr><tr valign="top"><td colspan="2"><h4>' . $desc . '</h4>';

case 'checkbox':

	echo '<input class="checkbox' . $field_class . '" type="checkbox" id="' . $id . '" name="mytheme_options[' . $id . ']" value="1" ' . checked( $options[$id], 1, false ) . ' /> <label for="' . $id . '">' . $desc . '</label>';


case 'select':
	echo '<select class="select' . $field_class . '" name="mytheme_options[' . $id . ']">';

	foreach ( $choices as $value => $label )
		echo '<option value="' . esc_attr( $value ) . '"' . selected( $options[$id], $value, false ) . '>' . $label . '</option>';

	echo '</select>';

	if ( $desc != '' )
		echo '<br /><span class="description">' . $desc . '</span>';


case 'radio':
	$i = 0;
	foreach ( $choices as $value => $label ) {
		echo '<input class="radio' . $field_class . '" type="radio" name="mytheme_options[' . $id . ']" id="' . $id . $i . '" value="' . esc_attr( $value ) . '" ' . checked( $options[$id], $value, false ) . '> <label for="' . $id . $i . '">' . $label . '</label>';
		if ( $i < count( $options ) - 1 )
			echo '<br />';

	if ( $desc != '' )
		echo '<br /><span class="description">' . $desc . '</span>';


case 'textarea':
	echo '<textarea class="' . $field_class . '" id="' . $id . '" name="mytheme_options[' . $id . ']" placeholder="' . $std . '" rows="5" cols="30">' . wp_htmledit_pre( $options[$id] ) . '</textarea>';

	if ( $desc != '' )
		echo '<br /><span class="description">' . $desc . '</span>';


case 'password':
	echo '<input class="regular-text' . $field_class . '" type="password" id="' . $id . '" name="mytheme_options[' . $id . ']" value="' . esc_attr( $options[$id] ) . '" />';

	if ( $desc != '' )
		echo '<br /><span class="description">' . $desc . '</span>';


Subheadings Within a Section

In the switch statement, I added a “heading” type as an option. This is useful for dividing up long sections. Since a setting is displayed within a table cell on the right-hand side, I had to cheat the system a bit here. I close the table cell and the table row, and then open a new row with a cell spanning two columns. Then I put an h4 inside to give it a consistent appearance and (somewhat) proper semantics. To avoid displaying a label on the left, the title is left blank, and the text for the heading is put in the description.

6. Custom Javascript and Styling

At this point, the settings page is 100% functional and is visually consistent with the rest of the WordPress admin. You could stop right here and have a solid theme options page. If you want something with a little more pizzazz, then we have more coding to do.

jQuery Tabs

Go back to the add_pages() function and add this line of code under what’s already there:

add_action( 'admin_print_scripts-' . $admin_page, array( &$this, 'scripts' ) );

This calls a function in our class so we can insert our own Javascript, and it only applies it to our theme options page in the admin. Here’s that function:

/* jQuery Tabs */
public function scripts() {
	wp_print_scripts( 'jquery-ui-tabs' );

Now we need to add some jQuery code to our admin page. We will do that in the display_page() function. Replace everything inside that function with the following:

echo '<div class="wrap">
<div class="icon32" id="icon-options-general"></div>
<h2>Theme Options</h2>
<form action="options.php" method="post">';

	settings_fields( 'mytheme_options' );
	echo '<div class="ui-tabs">
		<ul class="ui-tabs-nav">';

	foreach ( $this->sections as $section )
		echo '<li><a href="#' . strtolower( str_replace( ' ', '_', $section ) ) . '">' . $section . '</a></li>';

	echo '</ul>';
	do_settings_sections( $_GET['page'] );

	echo '</div>
	<p class="submit"><input name="Submit" type="submit" class="button-primary" value="' . __( 'Save Changes' ) . '" /></p>

<script type="text/javascript">
	jQuery(document).ready(function($) {


Before we get to the jQuery, I’ve added a couple of things. The entire settings output is now wrapped in a div with class=”ui-tabs”. Then there’s an unordered list with class=”ui-tabs-nav” that loops through our sections to output a link for each one.

Now the jQuery magic. First, we need to make some modification to the code structure. Each section needs to be wrapped in a div with class=”ui-tabs-panel”, but we can’t access any of the code between the sections with just PHP. So we’ll search for the <h3> tags and use those as a marker for beginning a new section.

var wrapped = $(".wrap h3").wrap("<div class=\"ui-tabs-panel\">");
wrapped.each(function() {

Thanks, Nathan, for the tip on using nextUntil() instead of next()!

This wraps the section headings inside of the div, and then takes what was directly after the headings—the table with all the form fields in it—and puts it inside that div. The next step is to add id attributes to the headings, and apply a class to all but the first panel to hide it from view:

$(".ui-tabs-panel").each(function(index) {
	var str = $(this).children("h3").text().replace(/\s/g, "_");
	$(this).attr("id", str.toLowerCase());
	if (index > 0)

The .each() function loops through the panels. By adding the “index” attribute to the function, it generates an arbitrary counter that we can use to determine whether we’re on the first panel.

Finally, apply the actual tabs functionality to our modified HTML:

$(".ui-tabs").tabs({ fx: { opacity: "toggle", duration: "fast" } });

The parameters inside the .tabs() function can be altered to change the animation and speed of the transition between tabs. Check out the jQuery UI documentation to see what’s possible.

Placeholder Text

The default values of our text and textarea fields are currently just showing up as values that the user has to manually erase to enter their own information. This isn’t a horrible thing, but we can make it more user-friendly by adding some jQuery and styling to the mix. Add this to the jQuery block in display_page():

$("input[type=text], textarea").each(function() {
	if ($(this).val() == $(this).attr("placeholder"))
		$(this).css("color", "#999");

$("input[type=text], textarea").focus(function() {
	if ($(this).val() == $(this).attr("placeholder")) {
		$(this).css("color", "#000");
}).blur(function() {
	if ($(this).val() == "" || $(this).val() == $(this).attr("placeholder")) {
		$(this).css("color", "#999");

This takes any field with a default value in it and makes the text grey. The value then disappears when the user clicks on the field (only if the value was the default). When a user clicks out of a field, if the field is still empty, it re-inserts the value and makes it grey again.

Because we’re creating some HTML dynamically and it’s going to be styled after the fact, we use CSS to hide the elements on the page to avoid the “flash” of unstyled content when the page loads. We need to put in one line of jQuery to show that content again when the loading is complete.

$(".wrap h3, .wrap table").show();

There are some strange issues that show up in Firefox because of its autocomplete functionality. To fix that, add this to the jQuery block:

if ($.browser.mozilla) 
         $("form").attr("autocomplete", "off");

Thank you, Billyben, for pointing this out to me!

Custom Styling

All the hard work is done! Everything from here on out is just gravy. To add custom CSS, we need to call our own stylesheet on our admin page.

First, create a new CSS file in your theme folder, and name it what you like (I’ll use mytheme-options.css). Add this to the add_pages() function:

add_action( 'admin_print_styles-' . $admin_page, array( &$this, 'styles' ) );

Now add the styles() function to the theme options class:

/* Insert custom CSS */
public function styles() {

	wp_register_style( 'mytheme-admin', get_bloginfo( 'stylesheet_directory' ) . '/mytheme-options.css' );
	wp_enqueue_style( 'mytheme-admin' );


Any CSS you put in that stylesheet will now be applied to your theme options page. To get the look seen in the screenshot below, put this in the stylesheet:

.ui-tabs-nav {
	border-bottom: 1px solid #ccc;
	height: 27px;
	margin: 20px 0;
	padding: 0;

.ui-tabs-nav li {
	display: block;
	float: left;
	margin: 0;

.ui-tabs-nav li a {
	padding: 4px 20px 6px;
	font-weight: bold;

.ui-tabs-nav li a {
	border-style: solid;
	border-color: #CCC #CCC #F9F9F9;
	border-width: 1px 1px 0;
	color: #C1C1C1;
	text-shadow: rgba(255, 255, 255, 1) 0 1px 0;
	display: inline-block;
	padding: 4px 14px 6px;
	text-decoration: none;
	margin: 0 6px -1px 0;
	-moz-border-radius: 5px 5px 0 0;
	-webkit-border-top-left-radius: 5px;
	-webkit-border-top-right-radius: 5px;
	-khtml-border-top-left-radius: 5px;
	-khtml-border-top-right-radius: 5px;
	border-top-left-radius: 5px;
	border-top-right-radius: 5px;

.ui-tabs-nav li.ui-tabs-selected a,
.ui-tabs-nav li.ui-state-active a {
	border-width: 1px;
	color: #464646;

.ui-tabs-panel {
	clear: both;

.ui-tabs-panel h3 {
	font: italic normal normal 24px/29px Georgia,"Times New Roman","Bitstream Charter",Times,serif;
	margin: 0;
	padding: 0 0 5px;
	line-height: 35px;
	text-shadow: 0 1px 0 #fff;

.ui-tabs-panel h4 {
	font-size: 15px;
	font-weight: bold;
	margin: 1em 0;

.wrap h3, .wrap table {
	display: none;

Voilà! I used some CSS3 to achieve some nice visual effects, so there are no images required.This will make the page look consistent with the rest of the WordPress admin.

Theme Options screenshot

Now you have a functional, beautiful, kickass theme options page!

There are still a couple of small things to cover, so be sure to read the follow-up post to this tutorial.