Login Skin
The plugin ships with neutral defaults so it's drop-in usable on any site. Activate, set your logo, pick two colors, done.
by Josip Mestrovic · github.com/josipmestrovic/login-skin · website
Install
No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:
wp plugin install https://github.com/josipmestrovic/login-skin/archive/refs/heads/main.zipBrand the WordPress login screen — logo, background color, primary color, browser-tab title — from one Appearance page. No code, no tabs.
A lightweight, privacy-respecting WordPress plugin that replaces the
default wp-login.php look with a clean, brand-customizable one.
Configure everything from a single screen under Appearance → Brand
My Login and ship.
Status: v1.0.0 · License: GPL-2.0-or-later · Requires WordPress 6.0+ / PHP 7.4+
Why
The default WordPress login screen looks like… the default WordPress login screen. For sites that hand out user accounts (membership, e-commerce, client portals, multi-author blogs) that's a missed branding moment every time someone signs in.
Login Skin keeps the WordPress login flow exactly as it is — same URL, same form, same error handling — and just paints the visible parts to match your brand.
Features
Branding
- Custom logo from the WordPress Media Library
- Optional logo link (defaults to your site homepage)
- Primary brand color drives the submit button, focus ring, and links — pick one color, the rest follows
- Card border-radius control (0–60 px)
Background
- Solid color fills the entire login page
- More background modes (gradient, hero image with overlay) coming in a future update
Browser tab
- Custom tab title shown in the visitor's browser tab on
wp-login.php - Custom on-page copy (tagline, footer) coming next
Behavior
- Generic error messages — replaces "incorrect username" / "incorrect password" disclosures with one neutral message, defeating username-enumeration scrapers
- Card fade-in animation that automatically respects the visitor's
prefers-reduced-motionsystem setting
Advanced
- Custom CSS escape hatch scoped to
body.loginwith a four-layer defense-in-depth sanitiser:wp_strip_all_tagsremoves any HTML- residual
</>characters are stripped - legacy XSS vectors (
javascript:,vbscript:,expression(,behavior:,-moz-binding) are neutralised - hard cap at 20 KB to prevent option bloat / DoS Sanitiser is re-run at render time as well, so even option values modified directly via WP-CLI or DB stay safe.
Performance & privacy
- Lazy-loaded modules — a normal front-end request parses just one small defaults file and registers zero callbacks
- No external HTTP requests — every asset is local
- No telemetry, no tracking, no third-party scripts — ever
- Multisite-aware uninstall — every option (current + legacy pre-rename keys) is removed cleanly when the plugin is deleted
Installation
From WordPress.org
- In your WordPress dashboard, go to Plugins → Add New
- Search for Login Skin
- Click Install Now, then Activate
- Go to Appearance → Login Skin to configure
Manual
-
Download or clone this repository into
wp-content/plugins/:cd wp-content/plugins git clone https://github.com/josipmestrovic/login-skin.git -
Activate from the Plugins screen
-
Configure under Appearance → Login Skin
File structure
login-skin/
├── login-skin.php ← main plugin file (header + bootstrap)
├── uninstall.php ← option cleanup on delete (multisite-aware)
├── readme.txt ← WordPress.org-format readme
├── README.md ← this file
├── CHANGELOG.md
├── .distignore ← excluded from the wp.org dist zip
├── .gitignore
├── assets/
│ ├── admin.css ← settings-screen styles
│ └── login.css ← wp-login.php styles
└── includes/
├── defaults.php ← option schema + getter
├── settings-register.php ← register_setting + sanitisers
├── settings-page.php ← Appearance → Login Skin renderer
├── settings-assets.php ← admin enqueue + inline JS
├── login-render.php ← all login_* hooks
└── login-styles.php ← inline CSS-var block
Building the WordPress.org zip
If you have WP-CLI with dist-archive:
wp dist-archive .
If zip isn't on your PATH (typical on Windows / Local by Flywheel),
use this PowerShell one-liner from the plugin folder's parent
directory:
$skip = @(
'.git','.github','.gitignore','.gitattributes','.distignore',
'.editorconfig','.vscode','.idea','.DS_Store','Thumbs.db',
'node_modules','vendor','composer.json','composer.lock',
'package.json','package-lock.json','yarn.lock',
'phpcs.xml','phpcs.xml.dist','phpunit.xml','phpunit.xml.dist',
'.phpunit.result.cache','tests','docs','bin',
'README.md','CHANGELOG.md','CONTRIBUTING.md'
);
$src = 'login-skin';
$out = 'login-skin.zip';
if (Test-Path $out) { Remove-Item $out };
$files = Get-ChildItem $src -Recurse -Force | Where-Object {
$rel = $_.FullName.Substring((Resolve-Path $src).Path.Length + 1);
$top = $rel -split '[\\/]' | Select-Object -First 1;
return -not ($skip -contains $top -or $skip -contains $_.Name);
};
Compress-Archive -Path $files.FullName -DestinationPath $out -Force;
License
GPL-2.0-or-later — see LICENSE (or the License URI in the plugin
header) for the full text.