Manual:Skinning

From MediaWiki.org
Jump to: navigation, search
MediaWiki version: 1.18

This tutorial details the creation of a custom skin for MediaWiki. The skin is packaged up into a directory you can drop into a MediaWiki installation and require_once() to install just as any other extension.

The actual creation of the HTML, CSS, images, any mockups, or even the idea of what a skin should look like is out of the scope of this tutorial. You'll have to either have the markup, styles, and resources ready, or build up the empty boilerplate and start building the skin from there.

Design considerations[edit | edit source]

There is more to a MediaWiki skin that it might seem at the first glance. A design of a generally usable skin needs to include certain easily overlooked elements.

It's very easy to create a beautiful design for your site and only later realize that you forgot the page actions links (e.g. links for editing or deleting pages), created a layout that doesn't fit the edit form, revision differences view or special pages, or didn't reserve a place for any user or site notices.

You don't need to restrict yourself to the norm: feel free to relocate portions of areas into other spots, like taking the username/talk links out of personal URLs. The important thing is to make sure you don't leave out the things MediaWiki needs to output when you think up the design.

Body area and layout[edit | edit source]

When mocking up the body area and layout of your skin remember there are many special types of pages in MediaWiki: You can view a diff between two versions of an article, there's the edit page, and there are some special pages like recent changes to think of. Many of these can be very heavy pages, so much so that you may want to consider giving them a modified wider page in your design if you are doing a fixed width design. Consider including a mock up using recent changes or an edit page in the body area.

Subtitles and taglines[edit | edit source]

When placing the title in your design remember that MediaWiki has some subtitles that usually go below the title. These are usually in the form of a subpage hierarchy, redirected from line, diff/permalink navigation, or undelete line. You don't need to use the same type of attached title+subtitle+body typically used in MonoBook/Vector but do keep in mind where such subtitles will go and how they will look.

Site notices and user notifications[edit | edit source]

MediaWiki has three built-in types of notifications: the site notice, user notice for new talkpage messages, and a floating JavaScript message dynamic actions (like adding a page to your watchlist). When you mock up your design remember to plan where these notices will go and how will they look.

Search[edit | edit source]

MediaWiki has a built in search functionality, and the search form is a key part of skins. When designing your skin remember to include a search input in your design, including a style for the search bar that fits in with the rest of the skin. Consider the behavior of built-in search suggestions.

Personal tools[edit | edit source]

MediaWiki includes a bar with a number of personal tools (links to login/logout, user and talk page, watchlist, preferences; more can be added by extensions). You can implement custom behaviors for particular items, but remember that other links can be added to this bar and you should have some location for the rest of the functionality you didn't add custom stuff for in your skin.

Actions[edit | edit source]

MediaWiki includes a number of page specific actions, these are typically displayed as tabs on a page. You have two formats you can work with this in, a single flat list (see MonoBook for reference) and a set of lists categorized into namespaces, variants, views, and actions (see how Vector separates them into separate areas and menus). Whether you use a flat list, grouped lists, or even pull some of them out and make buttons be sure you have a place for these: they are an important part of MediaWiki and easy to forget about if you are used to doing things for other CMS like WordPress.

Navigation[edit | edit source]

MediaWiki includes a built-in sidebar for navigation. It isn't the only type of navigation you can add to a skin, however it's the only type currently with a built-in way of generating it. You can use whatever type of navigation you want in a design, but remember that if you don't use a sidebar then you'll either have to hardcode pieces of it or have someone program something like the sidebar's parser for your navigation for now.

That said, don't hesitate to come up with a new layout that uses whatever type of navigation fits your skin and site best.

Toolbox[edit | edit source]

MediaWiki skins usually also include the functionality that MonoBook and Vector group into a toolbox. These tools include generic things like a special pages link and other page specific things like permalinks, feed links, block links, and printable links. Like the personal tools you can move pieces of these wherever you want, but since more links can be added to these tools be sure to have a place for the rest of the links you have not moved.

Language links[edit | edit source]

MediaWiki has support for interlanguage links from one page to the same page in a wiki of another language. One of the skin's responsibilities is to define a place for this list of language lines to go. Be sure to consider a place for it, even if you just throw it in a sidebar like MonoBook and Vector do.

Footer icons and footer links (optional)[edit | edit source]

Skins typically include a series of links and icons. These include things like the license / copyright of the wiki, a "powered by" icon, links to about, disclaimer, and privacy policy. When designing your skin consider if you want these or have a reason to leave them out of your design. When including them also consider how you are going to style them.

Creating the skin[edit | edit source]

If you're a person who prefers learning from examples, a basic skin built according to this guide exists. See Skin:Example for details and downloads.

Skin structure[edit | edit source]

Introduction[edit | edit source]

Lets start with the skin name – we'll call ours "My Skin" for tutorial purposes – and the blank files. You should come up with a name of your own for your skin.

When building a skin you'll be working with your skin name in two forms (and a half):

  • The localized skin name is displayed to wiki users in their preferences, where they can choose the skin to use. It may contain anything whatsoever, and may be translated into other languages. It should be used as the name of skin description page here on mediawiki.org if you decide to create one.
  • The skin name, usually CamelCased, is used as a part of the names of PHP classes used by the skin. It may only contain Latin letters and numbers (A-Z, a-z, 0-9).
    • The lowercase skin name, identical to the skin name but lowercased, it is used for names of site-wide and user CSS/JS customization pages, for the name of the localization message used to define the localized skin name, and internally as value of the 'skin' user preference, parameter &useskin=, and identifier for the skin in JS and CSS requests.

In our example skin, these are, respectively, "My Skin", "MySkin" and "myskin".

The name of the subdirectory under skins/ where your skin will be placed should be equal to either the skin name or its lowercase version (this guide uses the camelcase skin name). The same name should also be used for the main .php file of the skin (see below) and as the name of the repository if you put the skin in version control.

Wherever we use "My Skin", "MySkin" or "myskin" in examples, remember to replace those with the name of your own skin.

To start building your skin, create a new folder in the skins/ folder using the key for your skin, in our case skins/MySkin/.

Boilerplate and definitions[edit | edit source]

For this tutorial you should have the following blank files and empty subdirectories to start with. If you are using non-ANSI characters, ensure that the files are coded with UTF-8 (without BOM).

  • skins/MySkin/MySkin.php: the entry point containing metadata and definitions of resources used by your skin
  • skins/MySkin/MySkin.skin.php: will contain the actual skin code
  • skins/MySkin/i18n/: directory for localisation messages and their translations

For skins/MySkin/MySkin.php you should probably start with a documentation header:

<?php
/**
 * My Skin skin
 *
 * @file
 * @ingroup Skins
 * @author Daniel Friesen (http://www.mediawiki.org/wiki/User:Dantman)
 * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later
 */

The practice of including this metadata can help with documentation generation. The @file and @ingroup are organizational and should be left as-is, while you'll want to customize the @author and @license to what's correct for your skin. You can note the typical practice for each is @author Name (URL) or @author Name (email@example.com) and @license URL name.

Like extensions you may want to include a quick bit of boilerplate to prevent running standalone and credits so when installed people know who authored your skin:

if ( !defined( 'MEDIAWIKI' ) ) {
	die( 'This is an extension to the MediaWiki package and cannot be run standalone.' );
}
 
$wgExtensionCredits['skin'][] = array(
	'path' => __FILE__,
	'name' => 'My Skin', // name as shown under [[Special:Version]]
	'namemsg' => 'skinname-myskin', // used since MW 1.24, see the section on "Localisation messages" below
	'version' => '1.0',
	'url' => 'https://www.mediawiki.org/wiki/Skin:MySkin',
	'author' => '[https://mediawiki.org/wiki/User:Dantman Daniel Friesen]',
	'descriptionmsg' => 'myskin-desc', // see the section on "Localisation messages" below
	'license' => 'GPL-2.0+',
);

Be sure to customize things to what's correct for you too.

Next we want the boilerplate to declare our skin's name, autoloaded classes, and localisation messages (the content of the files and directories references here is described in the latter sections):

$wgValidSkinNames['myskin'] = 'MySkin';
 
$wgAutoloadClasses['SkinMySkin'] = __DIR__ . '/MySkin.skin.php';
$wgMessagesDirs['MySkin'] = __DIR__ . '/i18n';

The value provided in the $wgValidSkinNames line, prepended with 'Skin', is used as the name of your skin's mail PHP class: the name of the class for your skin must be "Skin{Your Identifier}" (in this example, "SkinMySkin"). Keep that in mind since that means that you do want to replace the "MySkin" in the "SkinMySkin" above with the identifier for your skin, and likewise you will want to do so in the class names in the .skin.php file.

Skin styles and scripts (ResourceLoader modules)[edit | edit source]

To provide styles and/or scripts for your skin, you must define ResourceLoader modules that include them.

$wgResourceModules['skins.myskin'] = array(
	'styles' => array(
		'MySkin/resources/screen.css' => array( 'media' => 'screen' ),
	),
	'remoteBasePath' => &$GLOBALS['wgStylePath'],
	'localBasePath' => &$GLOBALS['wgStyleDirectory'],
);

The 'styles' array itself does not have to be a single stylesheet, ResourceLoader combines all the stylesheets into a single stylesheet so you can separate your stylesheets into as many separate files as you want. You'll probably want to read up on the ResourceLoader because it has other features like @embed and RTL flipping to consider when writing the CSS for your skin,


MediaWiki version: 1.22

MediaWiki 1.22 added support for .less files as well as .css files. (You can't use them if you intend your skin to be compatible with earlier MediaWiki versions.)


MediaWiki version: 1.23

MediaWiki 1.23 includes 3 ResourceLoader modules that you can import to get some common content styling without having to copy it into your own CSS: 'mediawiki.skinning.elements', 'mediawiki.skinning.content' and 'mediawiki.skinning.interface', which provide progressively more default styling. They can be loaded like any other module (see the example in "Skin code" section below). We will use 'mediawiki.skinning.interface'. Note: you can't use these ResourceLoader modules if you'd like your skin to be compatible with earlier MediaWiki versions.


MediaWiki version: 1.24

MediaWiki 1.24 will support a simpler way to define the modules (note the changes on highlighted lines):

$wgResourceModules['skins.myskin'] = array(
	'styles' => array(
		'resources/screen.css' => array( 'media' => 'screen' ),	),
	'remoteSkinPath' => 'MySkin',	'localBasePath' => __DIR__,);

Only add the module for scripts if you actually need custom JavaScript:

$wgResourceModules['skins.myskin.js'] = array(
	'scripts' => array(
		'MySkin/resources/cool.js',
		'MySkin/resources/awesome.js',
	),
	'dependencies' => array(
		// In this example, awesome.js needs the jQuery UI dialog stuff
		'jquery.ui.dialog',
	),
	'remoteBasePath' => &$GLOBALS['wgStylePath'],
	'localBasePath' => &$GLOBALS['wgStyleDirectory'],
);

Localisation messages[edit | edit source]

MediaWiki version: 1.23

The next thing you'll want to deal with is the i18n files. These are stored in the JSON format.

At minimum you are going to need two i18n messages in English: One for the skin name so that it can be displayed right in the lists of skins in preferences where the user selects his skin, and another for the credits that will be visible on Special:Version.

Create a file skins/MySkin/i18n/en.json with the following contents. You can add translations by adding further files named after language codes, e.g. 'de' for German, 'es' for Spanish. A special "language code" of 'qqq' is used to provide documentation for the messages.

{
	"@metadata": {
		"authors": [ "John Doe" ]
	},
	"skinname-myskin": "My Skin",
	"myskin-desc": "Some skin of mine"
}

You'll want to customize the keys and contents of both messages (myskin-desc is the description that will appear on Special:Version) and the authorship metadata.


MediaWiki version: 1.17

The above way is only compatible with MediaWiki 1.23 and later. To provide compatibility with earlier versions up to and including MediaWiki 1.17, include a compatibility shim file generated by the generateJsonI18n.php maintenance script, with the following call: php maintenance/generateJsonI18n.php --shim-only skins/MySkin/MySkin.i18n.php skins/MySkin/i18n/ and declare it by adding another line in main skin file below $wgMessagesDirs:

$wgExtensionMessagesFiles['MySkin'] = __DIR__ . '/MySkin.i18n.php';

Skin code[edit | edit source]

The last PHP file to deal with now is the MySkin.skin.php file where the bulk of our actual skin is.

For good practice we'll want to open this up with the usual header:

<?php
/**
 * Skin file for skin My Skin.
 *
 * @file
 * @ingroup Skins
 */
 
// [...]

And now we want to open up a SkinTemplate class:

/**
 * SkinTemplate class for My Skin skin
 * @ingroup Skins
 */
class SkinMySkin extends SkinTemplate {
	var $skinname = 'myskin', $stylename = 'MySkin',
		$template = 'MySkinTemplate', $useHeadElement = true;
 
	/**
	 * Add JavaScript via ResourceLoader
	 *
	 * Uncomment this function if your skin has a JS file or files.
	 * Otherwise you won't need this function and you can safely delete it.
	 *
	 * @param OutputPage $out
	 */
	/*
	public function initPage( OutputPage $out ) {
		parent::initPage( $out );
		$out->addModules( 'skins.myskin.js' );
	}
	*/
 
	/**
	 * Add CSS via ResourceLoader
	 *
	 * @param $out OutputPage
	 */
	function setupSkinUserCss( OutputPage $out ) {
		parent::setupSkinUserCss( $out );
		$out->addModuleStyles( array(
			'mediawiki.skinning.interface', 'skins.myskin'
		) );
	}
}
 
// [...]

You should remember the SkinMySkin from earlier that you want to update, you'll also want to update MySkinTemplate. A skin is built up of a SkinTemplate subclass and a template, the SkinTemplate inherits a lot of heavy functionality from the skin system itself, and the template handles the creation of the markup for the skin. You'll also want to update the $skinname and $stylename (the first declares the internal skin name used for $wgValidSkinNames again, the second declares the directory name used in $wgResourceModules definitions again). The setupSkinUserCss code imports the necessary ResourceLoader modules, among them the module "skins.myskin" from before; remember to update the key there!

/**
 * BaseTemplate class for My Skin skin
 *
 * @ingroup Skins
 */
class MySkinTemplate extends BaseTemplate {
	/**
	 * Outputs the entire contents of the page
	 */
	public function execute() {
		$this->html( 'headelement' ); ?>
 
// [...]
 
<?php $this->printTrail(); ?>
</body>
</html><?php
	}
}

This is the outermost part of the boilerplate, we'll be continuing in the "[...]" and adding the markup for your skin. Keep in mind that headelement defines the <body> tag itself, so you shouldn't include one (if you want to add some attributes or classes to the body tag, override the addToBodyAttributes method in your SkinTemplate).

Now we get into your actual skin-specific markup here. Because that's entirely dependent on the skin you are building the rest of the boilerplate will be small boilerplate focused around how to turn parts of your markup into functional parts of the skin.

To start with you should take your markup and insert it into the "// [...]". You may want to consider taking this time to also setup the CSS for your skin. Doing so should give you a skin that doesn't function but displays all the dummy markup you have inserted. It's a good way to test your skin with just dummy markup and then make pieces of it functional and test them piece by piece.

Adding skin elements[edit | edit source]

In the following sections I am going to be using a helper method $this->makeListItem( $key, $item ); a lot to generate items from lists of data. This helper intelligently knows how to properly format a list item and all the attributes it needs, as well as create the correct links and other contents that belong in the list item.

By default this method outputs a <li>. If you wish to output something other than a an unordered or ordered list of items makeListItem can accept an array of options as a third argument. You can use that to change the tag from a 'li' to something else with something like array( 'tag' => 'span' ). Naturally when doing this you would also change the boilerplate I give to output something other than a <ul> to surround the list.

User message (newtalk)[edit | edit source]

MediaWiki can output a message to a user about new messages on their talkpage. Historically MonoBook and Vector have outputted the newtalk message in the content area and for now they are still there for compatibility with some out-of-content area hacks used on Wikipedia. However practically the newtalk message may actually make more sense alongside the JavaScript message inside your skin.

The boilerplate to output it is:

<?php $this->html( 'newtalk' ); ?>

The newtalk message has no wrapping or style on its own, so you should wrap this in a block to style. The message doesn't always show up so be sure to wrap it in a conditional:

<?php if ( $this->data['newtalk'] ) { ?>...<?php } ?>

The MonoBook/Vector line of skins uses a div with a usermessage class for the block around the newtalk message. To output the newtalk message in a MonoBook/Vector like way you'll use:

<?php if ( $this->data['newtalk'] ) { ?><div class="usermessage"><?php $this->html( 'newtalk' ); ?></div><?php } ?>

Site notice[edit | edit source]

The boilerplate to output a wiki's sitenotice is:

<?php $this->html( 'sitenotice' ); ?>

Because you'll likely be wrapping the sitenotice in a block you'll probably want to test if the sitenotice is present, to do that you'll use this boilerplate

<?php if ( $this->data['sitenotice'] ) { ?>...<?php } ?>

The MonoBook/Vector line of skins uses a div with a siteNotice id for the block around the sitenotice. To output the sitenotice in a MonoBook/Vector like way you would use:

<?php if ( $this->data['sitenotice'] ) { ?><div id="siteNotice"><?php $this->html( 'sitenotice' ); ?></div><?php } ?>

Site name[edit | edit source]

Though no core skin uses it, the boilerplate to output a wiki's sitename is:

<?php $this->text( 'sitename' ); ?>

Logo and main page link[edit | edit source]

The logo is comprised primarily of the key for the logo, the key for the main page's URL, and a call to insert a tooltip and accesskey for the logo.

<a
	href="<?php echo htmlspecialchars( $this->data['nav_urls']['mainpage']['href'] ) ?>"
	<?php echo Xml::expandAttributes( Linker::tooltipAndAccesskeyAttribs( 'p-logo' ) ) ?>
>
	<img
		src="<?php $this->text( 'logopath' ) ?>"
		alt="<?php $this->text( 'sitename' ) ?>"
	/>
</a>
  • The $this->data['nav_urls']['mainpage']['href'] references the main page's URL.
  • The $this->text( 'logopath' ) references the path to the logo's image.
    • If you use different code structure, you'll use $this->data['logopath'] as a raw URL to the image. Remember to HTML-escape it (the text() method does it automatically).
  • Linker::tooltipAndAccesskeyAttribs( 'p-logo' ) creates an array containing a tooltip and associated accesskey. 'p-logo' is the id typically used for the logo so that's the key that is used by MW to define the accesskey and tooltip.
  • If you output a second logo in the page you should avoid re-using the accesskey. Use Linker::titleAttrib( 'p-logo' ) as a value of the title attribute instead of the Linker::tooltipAndAccesskeyAttribs call.
  • Linker::tooltipAndAccesskeyAttribs returns an associative array of attributes that can be used with the Html:: interface, here it is expanded to HTML code with Xml::expandAttributes.
  • Be sure to remember to HTML-escape the href and logopath, either using htmlspecialchars or text().

Keep in mind a something about the logo itself. We currently only have one config for logopath. As a result the same logo will be used in every skin, that includes the built-in skins. MediaWiki's built-in skins generally have a logo area that restricts logo sizes to around 155x155px. If your skin has a logo area intended for a logo of a different size you may want to consider including a separate config variable to let users of your skin define a skin-specific logo.

Title[edit | edit source]

The title of the page is included into the skin's keys as HTML. Generally a title is plain text but a limited set of HTML formatting may be permitted inside of a DISPLAYTITLE and some extensions may add HTML to it.

The boilerplate to output the page's title is:

<?php $this->html( 'title' ); ?>

MonoBook and Vector output the title inside of a firstHeading h1, the full code to output the title in a MonoBook/Vector way is:

<h1 id="firstHeading" class="firstHeading"><?php $this->html( 'title' ) ?></h1>

The content area[edit | edit source]

The content area in MediaWiki usually has some extra styles like icons on external links.

MonoBook and Vector both have a <div id="content"> and a <div id="bodyContent"> though MonoBook styles stuff inside bodyContent while Vector styles stuff inside content.

Whether you keep these ids or not you should consider adding a class="mw-body" wherever you do decide links should be styled. 1.19 has some stylesheets you can opt-in to that will use that class.

Tagline[edit | edit source]

MediaWiki has a "Tagline" which usually goes right below the title, however is also typically hidden by default. The text usually goes something like "From Your Wiki's Name". Some wiki like to show it and style it, though the primary purpose of this seems not to be for normal viewing, but for printing to identify the source of the content. Be sure to include this and test your print styles, we'll go into more detail in a later print section.

The tagline's boilerplate is:

<?php $this->msg( 'tagline' ); ?>

You'll probably want to wrap it in a div, it's not optional so you don't need a test. MonoBook/Vector uses a siteSub id and use this boilerplate:

<div id="siteSub"><?php $this->msg( 'tagline' ); ?></div>

Note that Vector does seem to wrap this inside a test to only output it on articles:

<?php if ( $this->data['isarticle'] ) { ?>...<?php } ?>

Subtitles[edit | edit source]

Besides the tagline MediaWiki has two subtitles below the title to take into account. One is used for various things like the subpage hierarchy and redirected from line while the other is specifically for the undelete message.

The two pieces to output the subtitle lines are:

<?php $this->html( 'subtitle' ); ?>
<?php $this->html( 'undelete' ); ?>

Naturally both of these are optional so you'll want to test for them:

<?php if ( $this->data['subtitle'] ) { ?>...<?php } ?>
<?php if ( $this->data['undelete'] ) { ?>...<?php } ?>

MediaWiki/Vector uses contentSub and contentSub2 ids for these messages:

<?php if ( $this->data['subtitle'] ) { ?><div id="contentSub"><?php $this->html( 'subtitle' ); ?></div><?php } ?>
<?php if ( $this->data['undelete'] ) { ?><div id="contentSub2"><?php $this->html( 'undelete' ); ?></div><?php } ?>

Body text[edit | edit source]

The body of the page is dead simple to output, you might even want to do this first:

<?php $this->html( 'bodytext' ) ?>

Categories[edit | edit source]

MediaWiki doesn't have a good way to output categories besides the built-in catlinks block. Unfortunately the catlinks block includes an id, so for now we're stuck outputting a catlinks block in all skins and being limited to only one list of categories.

To output this block use:

<?php $this->html( 'catlinks' ); ?>

dataAfterContent[edit | edit source]

MediaWiki skins have another little hack in place. Extensions have the ability to add blocks which should go somewhere after the body text, but they aren't supposed to go before the catlinks block. Because the catlinks block typically goes right after the body text we have a separate key to insert these.

<?php $this->html( 'dataAfterContent' ); ?>

Be sure to include this or else some extensions may not work properly in your skin.

Personal tools[edit | edit source]

BaseTemplate includes some helpers that make building a list of personal tool links easy. Vector and MonoBook display the personal tools in the far upper corner of the page. They generally contain things like the login link, logout link, the user's username with a link to their userpage, talkpage, watchlist, preferences, and other links.

This boilerplate can output the personal tools as an unordered list.

<ul>
<?php
	foreach ( $this->getPersonalTools() as $key => $item ) {
		echo $this->makeListItem( $key, $item );
	}
?>
</ul>

If you want to omit some items from the personal tools you can set $this->getPersonalTools() to a variable and modify that then use that in the loop instead. The personal tools array uses keys so it's easy to unset or modify something.

Content actions[edit | edit source]

As of 1.18 MediaWiki supports the use of two different lists to access what is usually outputted as tabs for a page. content_actions provides these tabs as a single flat array, this was the original format of the actions and is used by MonoBook. The other is content_navigation which was introduced by Vector. content_navigation provides the list of tabs as a categorized array containing the categories 'namespaces', 'views', 'actions', and 'variants' with the tabs that are put into content_actions nicely separated into categories. If you want to differentiate between any of the tabs in any way at all, be it some special styling, or outputting them in separate lists, you are better off making use of the new content_navigation. Note that the content_navigation also includes a redundant 'Read' tab in views which is omitted in the flat content_actions list.

This is better explained with a number of examples.

This example outputs just the namespaces category of links.

<ul>
<?php
	foreach ( $this->data['content_navigation']['namespaces'] as $key => $tab ) {
		echo $this->makeListItem( $key, $tab );
	}
?>
</ul>

You can use this to output these categories wherever you want in whatever order you want.

This example outputs all the categories of links as separate lists:

<?php foreach ( $this->data['content_navigation'] as $category => $tabs ) { ?>
<ul>
<?php
	foreach ( $tabs as $key => $tab ) {
		echo $this->makeListItem( $key, $tab );
	}
?>
</ul>
<?php } ?>

This example outputs all the categories of links as one unified list. This will include the slightly redundant 'Read' tab, however you can use the separation of categories to add separators or other things wherever you want.

<ul>
<?php
	foreach ( $this->data['content_navigation'] as $category => $tabs ) {
		foreach ( $tabs as $key => $tab ) {
			echo $this->makeListItem( $key, $tab );
		}
	}
?>
</ul>

This example outputs the flat content_actions list as a single list:

<ul>
<?php
	foreach ( $this->data['content_actions'] as $key => $tab ) {
		echo $this->makeListItem( $key, $tab );
	}
?>
</ul>

For most of these you'll probably want to add something like a CSS class or id based off the $category to target with styles as you please.

Sidebar[edit | edit source]

Currently for navigation MediaWiki only supports a built-in sidebar. For other types of navigation you can hardcode or preferably have someone implement the code to support your own custom navigation message. For now all I can show is the sidebar boilerplate.

<?php
foreach ( $this->getSidebar() as $boxName => $box ) { ?>
<div id="<?php echo Sanitizer::escapeId( $box['id'] ) ?>"<?php echo Linker::tooltip( $box['id'] ) ?>>
	<h5><?php echo htmlspecialchars( $box['header'] ); ?></h5>
<?php
	if ( is_array( $box['content'] ) ) { ?>
	<ul>
<?php
		foreach ( $box['content'] as $key => $item ) {
			echo $this->makeListItem( $key, $item );
		}
?>
	</ul>
<?php
	} else {
		echo $box['content'];
	}
} ?>

The boilerplate is of course a little plain. I've left it with the typical id and tooltip but you may want to add a class or something. If you don't care about the ability to customize the output of lists you can use $this->getSkin( array( 'htmlOnly' => true ) ) and drop the whole is_array test and just keep the <?php echo $box['content']; ?>.

Some notes for those who have seen old skin boilerplate code. The toolbox and language boxes are now part of the array returned by getSidebar() you don't need to special case them, just keep the code to output the content of a sidebar block and they'll be outputted fine. The search block however is absent by default. If you want it to output you'll need to use array( 'search' => true' ) and special case the 'SEARCH' $boxName to output a search form and probably also a <label> in the header.

Language links[edit | edit source]

A MediaWiki page may have links to the same page in other languages on other wiki when interlanguage links are used in the page. If you do this traditionally like MonoBook and Vector you may output this as part of the sidebar.

The boilerplate to output the list of language links for a page is:

<ul>
<?php
	foreach ( $this->data['language_urls'] as $key => $langLink ) {
		echo $this->makeListItem( $key, $langLink );
	}
?>
</ul>

Since the language links are often not present you may want to display the ui around them conditionally:

<?php if ( $this->data['language_urls'] ) { ?>
	// [...]
<?php } ?>

Toolbox[edit | edit source]

The Mediawiki toolbox contains various links. Some are general links like a links to a list of Special Pages so a user always has a way to access them. Others are page-sensitive links like permalinks, printable links, block links, feed links, and a link to a list of pages linking to the current page.

The boilerplate to output the toolbox is:

<ul>
<?php
	foreach ( $this->getToolbox() as $key => $tbitem ) {
		echo $this->makeListItem( $key, $tbitem );
	}
	wfRunHooks( 'SkinTemplateToolboxEnd', array( &$this ) );
?>
</ul>

The SkinTemplateToolboxEnd hook is included for compatibility with some extensions. As more skins use the BaseTemplateToolbox hook and more extensions switch to using it we may be able to drop the hook and no longer include it.

Like personal URLs the array that $this->getToolbox(); returns can be set to a variable can can be easily modified and unset if you want to move parts of the toolbox elsewhere in your skin.

Search form[edit | edit source]

MediaWiki skins usually include a search form that can be used to search the wiki. If the feature is enabled and the form is setup correctly this form usually also supports search suggestions. MonoBook does this as part of the sidebar, while other skins such as Vector have a dedicated place for the search form.

To start the search form lets begin with the form itself:

<form action="<?php $this->text( 'wgScript' ); ?>">
	<input type="hidden" name="title" value="<?php $this->text( 'searchtitle' ) ?>" />
	// [...]
</form>

A search form itself uses the HTTP GET method rather than a POST. As a result you can't include any query arguments in the action="". So a search form is built initially of a form with the action pointed to the wiki's index.php and a hidden input with the search page's title to ensure it's in the URL.

You should probably give the form itself an id. Keep in mind that MediaWiki supports autocompletion of the search form. Right now in 1.18 MediaWiki will only look for search forms with one of the id's "searchform" "searchform2" "powersearch" or "search". So the best practice is to use id="searchform" for your form. Hopefully I may expand that later in MediaWiki to support search forms better. I think I'll use class="mw-search", so you might as well include that as well in your form as well.

Moving on, naturally every search form needs a text field to enter a search query into. At it's base level you can use this helper method to build a search input:

<?php echo $this->makeSearchInput(); ?>

However this helper supports an array input to change the intput that is output. I'll explain some of the common important points:

Firstly the input that is output by default uses type="search". Note that in the browsers that support this they usually give a search input special styling. As a result if you are doing special styling like what Vector does for it's simple search you may want to override this and force MediaWiki to output a text input instead of a search input:

<?php echo $this->makeSearchInput( array( 'type' => 'text' ) ); ?>

Another thing to keep in mind is you may want to use an id on your search input. You'll probably have some label nearby like "Search:" or something, at the very least it may be one that is hidden but is still around for accessibility purposes. You'll probably need an id to target the input with a label if you don't put the input inside the label itself. This is especially true if you put the label inside a header like vector does. The search input's helper also accepts an id to use. For example MonoBook and Vector use the id 'searchInput' to output a search input with the searchInput id you can use:

<?php echo $this->makeSearchInput( array( 'id' => 'searchInput' ) ); ?>

Note that the array is basically a list of attributes that are modified with the pieces important to a search input then passed to the Html:: method that generates the input. This means you can add just about any html attribute that you want. Feel free to use it to add a CSS class, a inline style, some html5 data- attribute, or so on.

Next we're going to need a search button so that the user can submit the search. However this helper has 3 different modes to keep in mind. MediaWiki supports two forms of search. The default "Go" search which may redirect directly to a page if the search term matches a page's title. And a "Full Text" search that will always go to the search page. The search input's helper supports both a 'go' mode and a 'fulltext' mode. The 3rd mode is an 'image' mode which outputs a 'go' mode button but does it using an image instead of text. Vector uses this last mode to create it's simplesearch icon. This helper also accepts an array of attributes as the second argument. The difference here is that in the 'image' mode the required 'src' attribute and optional 'alt' attribute go on the <img> while the rest of the attributes you specify go on the <button>. Here are some examples:

// A "Go" button
<?php echo $this->makeSearchButton( 'go' ); ?>
// A Full Text "Search" button
<?php echo $this->makeSearchButton( 'fulltext' ); ?>
// A Go button using the button text "Search"
<?php echo $this->makeSearchButton( 'go', array( 'value' => $this->translator->translate( 'searchbutton' ) ) ); ?>
// A image search button using the images/searchbutton.png image in your skin's folder.
<?php echo $this->makeSearchButton( 'image', array( 'src' => $this->getSkin()->getSkinStylePath( 'images/searchbutton.png') ) ); ?>

The tradition in MonoBook and Vector for id's here is to use searchGoButton and mw-searchbutton for a pair of Go and Full Text Search buttons, both with a searchButton class. And to use the id searchButton for a Vector simple search style image button. The examples for those cases:

// A "Go" button
<?php echo $this->makeSearchButton( 'go', array( 'id' => 'searchGoButton', 'class' => 'searchButton' ) ); ?>
// A Full Text "Search" button
<?php echo $this->makeSearchButton( 'fulltext', array( 'id' => 'mw-searchButton', 'class' => 'searchButton' ) ); ?>
// A image search button using the images/searchbutton.png image in your skin's folder.
<?php echo $this->makeSearchButton( 'image', array( 'id' => 'searchButton', 'src' => $this->getSkin()->getSkinStylePath( 'images/searchbutton.png') ); ?>

There's no standard for toggling between a classic Go+Search and a simple search as that introduction was specific to Vector. You're also free to just have a plain go button with a "Search" title if it fits your theme. The old monobook had a config option to only have a Go button and instead have a powersearch link but I won't go into that here.

Footer links[edit | edit source]

A MediaWiki skin may contain a common list of links and pieces of text intended to be in the footer. These footer links may contain things like a license line and other links like about, privacy policy, and disclaimer links.

The footerlinks array is returned by the $this->getFooterLinks(); helper which also accepts a "flat" argument to return a flat array instead of a categorized one.

The boilerplate to output a collection of footerlinks lists is:

<?php
foreach ( $this->getFooterLinks() as $category => $links ) { ?>
<ul>
<?php
	foreach ( $links as $key ) { ?>
	<li><?php $this->html( $key ) ?></li>
 
<?php
	} ?>
</ul>
<?php
} ?>

To output the footer links as a flat array you can instead us

<ul>
<?php foreach ( $this->getFooterLinks( 'flat' ) as $key ) { ?
	<li><?php $this->html( $key ) ?></li>
 
<?php } ?>
</ul>

Remember that like content_navigation this is a categorized list, however in this case it's not a fixed set of categories. You may want to output these as separate lists, give each list a class or id based on the category, and do the same for the list items as well.

Footer icons[edit | edit source]

The footer may also contain a common list of icons in it. Included in these icons are MediaWiki's powered by icon, optionally a "copyright" icon with an icon representing the license the wiki has chosen. As well as any other icon that a user or extension has defined with $wgFooterIcons.

The footer icons list is categorized into groups.

To output a list of only icons where each group is a list item containing one or more icons you can use:

<ul>
<?php
	foreach ( $this->getFooterIcons( 'icononly' ) as $blockName => $footerIcons ) { ?>
	<li>
<?php
		foreach ( $footerIcons as $icon ) {
			echo $this->getSkin()->makeFooterIcon( $icon );
		}
?>
	</li>
<?php
	} ?>
</ul>

Using the 'withoutImage' parameter to makeFooterIcon you can instead output a textual list instead of icons. Modern uses this kind of technique. Note that footer links has a textual representation of the wiki's license, so including the copyright/license icon as a second piece of text doesn't make that much sense. Hence to omit the copyright icon/text you can use the "nocopyright" parameter to getFooterIcons.

<ul>
<?php
	foreach ( $this->getFooterIcons( 'nocopyright' ) as $blockName => $footerIcons ) { ?>
	<li>
<?php
		foreach ( $footerIcons as $icon ) {
			echo $this->getSkin()->makeFooterIcon( $icon, 'withoutImage' );
		}
?>
	</li>
<?php
	} ?>
</ul>

Taking care of special cases[edit | edit source]

MediaWiki has a number of variances in what it displays depending on the page you are on. When finishing up you skin you should go around to the different parts of MediaWiki and ensure your skin looks correct in those cases.

  • Test the "Redirected from" line on a redirected page.
  • And test the redirect page itself.
  • View a page's permalink to ensure your skin works fine when viewing old revisions.
  • Test your wiki's skin on the edit, history, and diff pages.
  • Look at a deleted page to ensure the undelete line is present.
  • Double check the styles on Special:SpecialPages.
  • Test at least a few of the other special pages.
  • Test your skin's UI on pages as an anonymous user, a normal user, and as an administrator, each of which have different tabs.

In addition to these small things to test while building your skin there are a few major cases you should ensure function correctly when you've put your skin together.

Print mode[edit | edit source]

MediaWiki supports page printing with a print stylesheet that removes most of the ui, this allows users to simply print any page on the wiki without any special printing mode. The 'printable' mode does little but disable screen stylesheets and take the print stylesheets and make them apply to the screen, it's only around for users who still think that they need a separate mode to print.

You've likely added new interface pieces with new classes not included in the common printing stylesheet, hence you'll need to make some tweaks to ensure your skin looks correctly while printing.

To tweak things in print mode the best method of doing so is to add a print stylesheet. Go back to that resource loader array and add a new stylesheet with a 'print' media instead of 'screen'. From this stylesheet you can set display: none; on the parts of your interface that still show up in print mode. You may also wish to give some things like the tagline a better print style as well.

Alternatively you can add class="noprint" to the elements you need to hide from print mode.

Other languages[edit | edit source]

Use your i18n[edit | edit source]

MediaWiki has a great i18n system. Making use of it means that text in your skin can be translated into other languages letting your skin be used on more wiki and supporting visitors that come to your wiki who happen to speak a different preferred native language. So, please do make use of the i18n system if you add any pieces of text to your skin. And by using the i18n system wikis can customize the text that is outputted.

To define a new i18n key for your skin make use of the SkinName.i18n.php file we created. Adding new entries to the $messages['en'] array will define new messages in the i18n system. Keep in mind that these messages keys are global, so it's good practice to prefix any key you use with a 'skinkey-' to avoid conflicts.

After you've defined a new message you can use it in your skin with:

<?php $this->msg( 'msg-key' ); ?>

If you want to consider the message a block of wikitext to be parsed instead of outputted as escaped plain text you can use this:

<?php $this->msgWiki( 'msg-key' ); ?>

Do keep in mind though that this invokes the parser and may not be cached so nicely, so overusing msgWiki on a popular wiki can give you a performance hit, especially if you use anything advanced in the wikitext like parser functions and other extensions.

Language variants[edit | edit source]

MediaWiki supports the concept of 'variants' for some languages. Some languages such as Kazakh and Serbian support multiple writing scripts. MediaWiki includes a language converter that automatically converts content between these scripts. When on a wiki using a language that supports variants MediaWiki includes tabs in the 'variants' category of tabs.

When you're done with the primary coding and styling of your skin you should consider testing to ensure you have variants tabs outputting correctly. You can do this by setting your development wiki's content to 'sr' or 'zh' temporarily.

Right-to-left (RTL) languages[edit | edit source]

Besides the many languages such as English which are written left-to-right (LTR), there are some languages that MediaWiki supports which are written right-to-left (RTL).

Even more importantly than language variants, you should probably test your skins compatibility with RTL languages. Because MediaWiki 1.18 includes better directionality support that allows the page language direction to be based on the user's language rather than the content language you can test simply by including a uselang= parameter to a RTL language such as &uselang=fa (Persian) or &uselang=he Hebrew.

To make support for RTL languages easy ResourceLoader automatically flips many CSS properties automatically doing most of the work flipping the interface. There are however two cases where you may need to intervene and help directly to support RTL languages. Firstly ResourceLoader will flip things like margin-{left,right} and padding-{left,right} however it will not flip left/right margins and padding in the combined 'margin' and 'padding' properties, you may need to alter your CSS to use separated properties for left and right margins and padding. Secondly you may have some parts of the interface which may break when flipped and not work correctly in RTL, if you have a CSS property that is being flipped which needs to stay the same you can prefix it with /* @noflip */ to stop it from being flipped.

Accessibility[edit | edit source]

Another of MediaWiki's heavy focuses is accessibility. Accessibility is heavily considered whenever a new skin is added to core, however simply by not taking accessibility into account when you build a new skin you can easily ruin the accessibility of your wiki.

A full explanation on how to make a website / skin accessible is out of the scope of this tutorial. For in-depth information you should probably look at other things such as WebAIM's information and WAI. But I will cover skip links here.

Skip links — also known as jump links in parts of MediaWiki — are an accessibility pattern traditionally used to allow users without the use of a mouse to skip large blocks of navigation and go directly to the content instead of having to tab through or listen to them all before reaching the content.

You should consider reading up on the concept here: http://webaim.org/techniques/skipnav/

Traditionally they have been called "Skip" links and implemented as "Skip navigation" or "Skip to content". MediaWiki instead calls them "Jump" links and are implemented as "Jump to navigation". I don't know the origin of using jump instead of skip but it would likely be a good idea of keeping the pattern of creating "Skip ..." links when your navigation is first and your content comes later on the page, and "Jump ..." links when your content is the first thing on the page and your navigation comes later.

Skip links implementation is essentially the same as in a plain website. A div which you may or may-not hide containing a list of links pointing to ids you have in other spots in the markup to skip to that area.

While you're at it you may want to consider adding WAI-ARIA roles to your skin for the browsers that implement it.

Installing your skin[edit | edit source]

In case you didn't catch it from the very beginning of the tutorial. To install your skin include this inside your LocalSettings.php:

require_once( "$IP/skins/MySkin/MySkin.php" );

Replacing "MySkin" with your skin's name.

This pattern of skin works a little more like an extension, you have to require it, there is no skin autoloader now.

To try out your skin you can either change $wgDefaultSkin to make your new skin the default skin, go to Special:Preferences and change to the new skin, or follow the "Preview" link from Special:Preferences.