Maintaining a Beautiful
WordPress Admin UI

Helen Hou-Sandí
WordCamp Phoenix 2012

About Me

Web Design Engineer with 10up, LLC


@helenhousandi on Twitter


WordPress Core Contributor *

* Check out the 3.3 credits screen.
This dashed border tells us … that I am a developer, not a designer.
mdawaffe, WCSF 2011

* To be fair, there were color issues with the display

Decisions, not Options.

Design/UI to pay attention to:

WordPress.org UI Style Guide

Custom Content Types and
Post Meta

Post Type Icons

// Admin menu icon
register_post_type( 'event', array(
	...
	'menu_icon' => get_template_directory_uri() . '/images/icon-16.png';
));

// Screen icon
add_action( 'admin_head', 'hhs_event_screen_icon' );
function hhs_event_screen_icon() {
	$post_type = get_current_screen()->post_type;

	if ( 'event' != $post_type )
		return;
?>
<style type="text/css">
.icon32.icon32-posts-event {
	background: url(<?php echo get_template_directory_uri(); ?>/images/icon-32.png) !important;
}
</style>
<?php
}

Better Post Type Icons

// Don't set anything for the menu_icon arg in register_post_type()

add_action( 'admin_head', 'hhs_event_admin_menu_icon' );
function hhs_event_admin_menu_icon() {
?>
<style type="text/css">
#adminmenu #menu-posts-event div.wp-menu-image{
	background: transparent url(<?php echo get_template_directory_uri(); ?>/images/icon-16.png) no-repeat 6px -17px;
}
#adminmenu #menu-posts-event:hover div.wp-menu-image,
#adminmenu #menu-posts-event.wp-has-current-submenu div.wp-menu-image {
	background-position: 6px 7px;
</style>
<?php
}

Less is More

register_post_type( 'slide', array(
	...
	'show_in_menu' => 'themes.php',
	'show_in_nav_menus' => false,
	'supports' => array( 'thumbnail' )
) );

Add New / Edit screen, before

Featured Image metabox

add_action( 'add_meta_boxes_slide',  'hhs_slide_add_meta_boxes' );
add_filter( 'admin_post_thumbnail_html', 'hhs_slide_post_thumbnail_html' );

function hhs_slide_add_meta_boxes() {
	remove_meta_box( 'postimagediv', 'slide', 'side' );
	
	add_meta_box( 'postimagediv', 'Slide Image', 'post_thumbnail_meta_box', 'slide', 'normal', 'high' );
}

function hhs_slide_post_thumbnail_html( $output ) {
	global $post_type;
	
	// beware of translated admin
	if ( ! empty ( $post_type ) && 'slide' == $post_type ) {
		$output = str_replace( 'Set featured image', 'Select / Upload a slide image', $output );
	}
	
	return $output;
}

List table, before

List table, after

List table columns and row actions

add_filter( 'manage_edit-slide_columns', 'hhs_slide_edit_columns' );
add_action( 'manage_slide_posts_custom_column',  'hhs_slide_custom_columns' );

function hhs_slide_edit_columns( $columns ) {
	$columns = array(
		'cb' => '<input type="checkbox" />',
		'thumbnail' => 'Slide',
	);

	return $columns;
}

function hhs_slide_custom_columns( $column ) {
	global $post;
	switch ($column) {
		case 'thumbnail' :
			if ( has_post_thumbnail( $post->ID ) ) { // the current post has a thumbnail
				the_post_thumbnail( $post->ID );
			}
			else { // the current post lacks a thumbnail
				?>
				No image
				<?php
			}
			
			// add row_action links for Edit and Trash because there's no title column
			$post_type_object = get_post_type_object( $post->post_type );
			$can_edit_post = current_user_can( $post_type_object->cap->edit_post, $post->ID );
			$always_visible = false; // change to true to make it always show instead of on hover
			$actions = array();
			
			if ( $can_edit_post && 'trash' != $post->post_status ) {
				$actions['edit'] = '<a href="' . get_edit_post_link( $post->ID, true ) . '" title="' . esc_attr( __( 'Edit this item' ) ) . '">' . __( 'Edit' ) . '</a>';
			}
			if ( current_user_can( $post_type_object->cap->delete_post, $post->ID ) ) {
				if ( 'trash' == $post->post_status )
					$actions['untrash'] = "<a title='" . esc_attr( __( 'Restore this item from the Trash' ) ) . "' href='" . wp_nonce_url( admin_url( sprintf( $post_type_object->_edit_link . '&amp;action=untrash', $post->ID ) ), 'untrash-' . $post->post_type . '_' . $post->ID ) . "'>" . __( 'Restore' ) . "</a>";
				elseif ( EMPTY_TRASH_DAYS )
					$actions['trash'] = "<a class='submitdelete' title='" . esc_attr( __( 'Move this item to the Trash' ) ) . "' href='" . get_delete_post_link( $post->ID ) . "'>" . __( 'Trash' ) . "</a>";
				if ( 'trash' == $post->post_status || !EMPTY_TRASH_DAYS )
					$actions['delete'] = "<a class='submitdelete' title='" . esc_attr( __( 'Delete this item permanently' ) ) . "' href='" . get_delete_post_link( $post->ID, '', true ) . "'>" . __( 'Delete Permanently' ) . "</a>";
			}
			
			$action_count = count( $actions );
			$i = 0;
			$out = '<div class="' . ( $always_visible ? 'row-actions-visible' : 'row-actions' ) . '">';
			foreach ( $actions as $action => $link ) {
				++$i;
				( $i == $action_count ) ? $sep = '' : $sep = ' | ';
				$out .= "<span class='$action'>$link$sep</span>";
			}
			$out .= '</div>';
	
			echo $out;
			break;
	}
}

jQuery UI Example: Datepicker

Datepicker in a metabox

// can also use callback in register_post_type() instead
register_post_type( 'event', array(
		...
		'register_meta_box_cb' => 'hhs_event_add_meta_boxes' ),
	)
);

function hhs_event_add_meta_boxes( $post ) {
	add_meta_box( 'hhs-event-date', 'Event Information', 'hhs_event_datepicker_meta_box', 'event', 'side', 'default' );
}

function hhs_event_datepicker_meta_box( $post ) {
	$event_date = get_post_meta( $post->ID, 'hhs_event_date', true );
	
	// mid-page enqueueing came in 3.3
	wp_enqueue_script( 'jquery-ui-datepicker' );
	
	wp_nonce_field( plugin_basename(__FILE__), 'hhs_eventmeta_nonce' );
	?>
	<script type="text/javascript">
	jQuery(document).ready(function($) {
		$('.datepicker').datepicker({
			dateFormat : 'yy-mm-dd'
		});
	});
	</script>
	
	<p><label for="hhs_event_date">Event Date</label><br />
	<input type="text" class="datepicker" name="hhs_event_date" id="hhs_event_date"<?php if( $event_date ) echo ' value="' . esc_attr( $event_date) . '"'; ?> /></p>
	<?php
}

List table with custom sortable column

Add columns to the list table

add_filter( 'manage_edit-events_columns', 'hhs_event_edit_columns' );
add_action( 'manage_event_posts_custom_column',  'hhs_event_custom_columns' );

function hhs_event_edit_columns( $columns ) {
	// relabel title column
	$columns['title'] = 'Event Title';
	
	// remove published date column
	unset( $columns['date'] );
	
	// append new columns
	$columns['event-date'] = 'Event Date';
	
	return $columns;
}

function hhs_event_custom_columns( $column ) {
	global $post;
	$event_date = get_post_meta( $post->ID, 'hhs_event_date', true );
	
	switch ( $column ) {
		case 'event-date' :
			// escape as appropriate
			// the date shouldn't contain HTML
			echo esc_html( $event_date );
			break;
	}
}

Make a column sortable

add_filter( 'manage_edit-event_sortable_columns', 'hhs_event_sortable_columns' );
add_action( 'load-edit.php', 'hhs_event_edit_load' );

function hhs_event_sortable_columns( $columns ) {
	$columns['event-date'] = 'event-date';
	return $columns;
}

function hhs_event_edit_load() {
	add_filter( 'request', 'hhs_event_sort' );
}

function hhs_event_sort( $vars ) {
	// Check post type and for the orderby request
	if ( isset( $vars['post_type'] ) && 'event' == $vars['post_type'] &&
	     isset( $vars['orderby'] ) && 'event-date' == $vars['orderby'] ) {

		// Merge in with the rest of the query vars
		$vars = array_merge(
			$vars,
			array(
				'meta_key' => 'hhs_event_date',
				'orderby' => 'meta_value'
			)
		);
	}

	return $vars;
}

Chosen for multi-selects and taxonomies

Static front page metabox

Note: Only appears when a front page has already been selected.

add_action( 'add_meta_boxes', 'hhs_add_meta_boxes', 10, 2 );
function hhs_add_meta_boxes( $post_type, $post ) {
	$front_page = get_option( 'page_on_front' );

	if ( $post->ID === $front_page ) {
		add_meta_box( 'hhs_front_page_meta', 'Home Page Content', 'hhs_front_page_meta', 'page', 'normal', 'high' );

		// remove the editor
		remove_post_type_support( 'page', 'editor' );
	}
}

Better place for status-related meta

post_submitbox_misc_actions

<?php
add_action( 'post_submitbox_misc_actions', 'hhs_post_submitbox_misc_actions' );
function hhs_post_submitbox_misc_actions() {
	// restrict post type if needed
	if ( 'post' != get_post_type() )
		return;

	wp_nonce_field( 'hhs_post_submitbox_nonce', plugin_basename(__FILE__) );

	// the following inline styles may not be needed in 3.4
	// see http://core.trac.wordpress.org/ticket/19604
?>
	<div style="border-top: 1px solid #dfdfdf;">
		<div class="misc-pub-section misc-pub-section-last" style="border-top: 1px solid #fff;">
			<input type="checkbox" name="remove_claim_link" id="remove_claim_link" value="1" <?php checked( get_post_meta( get_the_ID(), 'remove_claim_link', true ) ); ?> />
			<label for="remove_claim_link">Claimed</label>
		</div>
	</div>
	<div style="border-top: 1px solid #dfdfdf;">
		<div class="misc-pub-section misc-pub-section-last" style="border-top: 1px solid #fff;">
			<input type="checkbox" name="is_featured" id="is_featured" value="1" <?php checked( get_post_meta( get_the_ID(), 'is_featured', true ) ); ?> />
			<label for="is_featured">Featured</label>
		</div>
	</div>
	<div style="border-top: 1px solid #dfdfdf;">
		<div class="misc-pub-section misc-pub-section-last" style="border-top: 1px solid #fff;">
			<input type="checkbox" name="is_premium" id="is_premium" value="1" <?php checked( get_post_meta( get_the_ID(), 'is_premium', true ) ); ?> />
			<label for="is_premium">Premium</label>
		</div>
	</div>
<?php
}

Default Content

// These are actual text, not placeholders - will save if not removed
// Also: default_excerpt
add_filter( 'default_title', 'hhs_event_default_title', 10, 2 );
function hhs_event_default_title( $title, $post ) {
	if ( 'event' == $post->post_type )
		$title = 'Event Title';

	return $title;
}
add_filter( 'default_content', 'hhs_event_default_content', 10, 2 );
function hhs_event_default_content( $content, $post ) {
	if ( 'event' == $post->post_type )
		$content = 'Event description';

	return $content;
}

// This is a placeholder
add_filter( 'enter_title_here', 'hhs_event_enter_title_here', 10, 2 );
function hhs_event_enter_title_here( $placeholder, $post ) {
	if ( 'event' == $post->post_type )
		$placeholder = 'Event Title';

	return $placeholder;
}

More things you can do

Plugins of Note

Related Presentations

Thank you! (Questions?)


Slides online at
http://slides.helenhousandi.com/wcphx2012.html