Understanding MediaWiki's Skin System: Customizing Your Wiki's Appearance
A technical look at the MediaWiki skin system: anatomy of a skin, minimal working examples, customization routes, and common pitfalls when building your own.
A MediaWiki skin controls how pages are presented: which layout, which stylesheets, which navigation elements exist around the content. The default install ships with Vector (legacy and the 2022 redesign), Timeless, MonoBook and Minerva Neue (the mobile skin used on Wikipedia). Skins matter beyond looks: they define reading flow, navigation density and mobile behavior, so a wiki's skin choice is partly a product decision. This article covers how the skin system works and how to build or customize your own.
Anatomy of a skin
A modern skin consists of three parts, all living in skins/YourSkinName/:
- The skin class — a PHP class extending
SkinTemplate(which itself extendsSkin), declaring the skin name and which template to use - The template class — a PHP class extending
BaseTemplate, whoseexecute()method renders the page structure: header, content area, sidebar, footer - ResourceLoader modules — CSS, LESS and JavaScript assets declared in the skin's
skin.json
Skins are registered with skin.json nowadays (manifest version 2), which also declares their ResourceLoader modules without touching LocalSettings.php:
{
"name": "MySkin",
"license-name": "GPL-2.0-or-later",
"type": "skin",
"ValidSkinNames": {
"myskin": "SkinMySkin"
},
"ResourceModules": {
"skins.myskin": {
"class": "ResourceLoaderSkinModule",
"features": [ "normalize", "elements", "interface", "toc" ],
"styles": [ "resources/skin.less" ]
}
}
}
A minimal skin
The skin class is deliberately thin — the logic lives in the template:
class SkinMySkin extends SkinTemplate {
public $skinname = 'myskin';
public $template = 'MySkinTemplate';
public function initPage( OutputPage $out ) {
parent::initPage( $out );
$out->addModuleStyles( [ 'skins.myskin' ] );
$out->addModules( [ 'skins.myskin.js' ] );
}
}
The template class receives all page data through the get() and html() helpers. A functional skeleton:
class MySkinTemplate extends BaseTemplate {
public function execute() {
$this->html( 'headelement' ); // <head>, title, scripts
echo '<div class="site-header">'
. $this->get( 'sitename' )
. '</div>';
echo '<div id="content">';
echo $this->get( 'newtalk' )
? '<div class="usermessage">You have new messages</div>'
: '';
echo '<h1 id="firstHeading">' . $this->get( 'title' ) . '</h1>';
echo $this->html( 'bodycontent' );
echo '</div>';
$this->html( 'bottomscripts' );
$this->html( 'footer' );
}
}
The data available to a template — page title, body, site name, navigation links, footer — is documented on Manual:Skinning, and the SkinTemplateGeneratePageActions/SkinTemplateNavigation hooks are how skins and extensions adjust the menus.
Customizing an existing skin instead
Most wikis never write a skin from scratch. Practical options, in increasing order of effort:
- Site-wide CSS —
MediaWiki:Common.cssplus theSkinModulevariables are enough for recoloring and small layout tweaks - Forking — copy an existing skin (Timeless is a popular base) and trim or extend it. Keep a clear internal name so MediaWiki does not confuse your fork with the original
- Community skins — Citizen (a modern, dark-mode capable skin) and Chameleon (Bootstrap-based, configuration-driven) cover many needs without custom code
Styling: ResourceLoader and LESS
ResourceLoader bundles, minifies and content-hashes assets, so browser caches are manageable without manual version bumping. During development, append ?debug=1 or set $wgResourceLoaderDebug = true; in LocalSettings.php to disable minification and caching. Core skins write stylesheets in LESS, and ResourceLoader compiles them on the fly; the newer approach is to expose design tokens as CSS custom properties so themes can be adjusted per-skin without recompiling.
Relative URLs inside stylesheets resolve against the stylesheet location, so images can be stored next to the CSS without absolute paths.
Testing a skin without disrupting users
A skin change can be previewed in three ways:
- Per-user —
Special:Preferences→ Appearance lets you test as another account - URL parameter — append
?useskin=myskinto any page (also works for logged-out users and is handy for screenshots) - Default —
$wgDefaultSkinswitches everyone; keep it a deliberate, announced change
For deployments, remember $wgSkipSkins, which hides skins from the preference list — useful while a new skin is still experimental.
Common pitfalls
- Template class mismatch — the
$templateproperty must exactly match the template class name, or MediaWiki fails with an "invalid skin template" error - Serving old CSS — after changing assets, modules are re-hashed automatically in production, but the page cache can still serve stale versions; purge the page or wait for the cache to expire
- Overriding core modules — re-declaring a module name like
mediawiki.utilbreaks dependents; always prefix your modules and skin idents with a unique name - Version compatibility — the skin system evolves with MediaWiki: one major upgrade introduced the templated
SkinModulefeatures flag, and 1.44 changed heading markup (skins must opt intosupportsMwHeadingfor the new HTML). Always match the skin branch to your MediaWiki version - Mobile behavior — desktop skins are not automatically responsive-friendly; test with the
?useskin=minervaequivalent or check your layout at small widths early
Where to start
Before writing code, decide whether you are solving a looks problem or a workflow problem. For the former, MediaWiki:Common.css goes a long way; for the latter, forking Timeless or adopting Citizen is usually faster than a custom skin. When you do write one, the reference implementations in the skins/ folder of any MediaWiki install — Vector's template and modules in particular — are the best documentation there is, alongside Manual:Skinning on mediawiki.org.