How to Customise the WordPress Login Page Without Plugins

How to Customise the WordPress Login Page Without Plugins

Last verified: September 22, 2026
9 min read
Guide
Security auditor
Full-stack developer

The default WordPress login screen (wp-login.php) is functional and recognisable. For a client project it still reads as “stock CMS”, not as their product. White-labelling the login screen is a small change with outsized polish: logo, colours, quieter errors, and CSS that only loads on that URL.

This guide uses core hooks only. No branding plugin. Put the code in a child theme or a tiny must-use / site plugin so a parent theme update does not wipe it. Related hardening beyond cosmetics sits with a proper WordPress security audit when the site already has exposure history.

#Why customise login at all

Editors and clients land on wp-login.php more often than on most marketing pages. A mismatched WordPress logo next to their brand colours breaks trust before they reach the dashboard. Agencies also use a branded login as a soft signal that the install was cared for, not left on defaults.

Customisation is not security by itself. Changing the logo does not stop credential stuffing. Pair branding with generic error text, rate limits (host or plugin), and 2FA on privileged accounts.

Typical brief from a client: “Make it look like our app.” That usually means logo, background, primary button colour, and footer links that point back to their domain. It does not mean rewriting authentication. Keep the scope honest in the estimate so nobody expects a SSO build from a CSS ticket.

Multisite networks often want one branded login for every site. Put shared CSS in a must-use plugin or a network-activated site plugin, and allow per-site logo overrides with a simple option or the Customizer only if editors truly need self-serve changes.

Hook login_enqueue_scripts and override the background image on #login h1 a.

function wppoland_login_logo() {
	?>
	<style type="text/css">
		#login h1 a,
		.login h1 a {
			background-image: url(<?php echo esc_url( get_stylesheet_directory_uri() . '/images/client-logo.svg' ); ?>);
			height: 65px;
			width: 320px;
			background-size: contain;
			background-repeat: no-repeat;
			padding-bottom: 30px;
		}
	</style>
	<?php
}
add_action( 'login_enqueue_scripts', 'wppoland_login_logo' );

Use esc_url on the image path. Prefer SVG or a retina-ready PNG; oversized JPEG logos look soft on HiDPI screens. Keep the file in the child theme so deploys stay predictable.

By default the logo links to wordpress.org. Point it at the site home:

function wppoland_login_logo_url() {
	return home_url( '/' );
}
add_filter( 'login_headerurl', 'wppoland_login_logo_url' );

Optionally filter login_headertext so the accessible title matches the client name instead of “Powered by WordPress”.

#Load CSS only on the login screen

Do not dump login rules into the public style.css. Enqueue a dedicated file on the same hook:

function wppoland_login_stylesheet() {
	wp_enqueue_style(
		'wppoland-custom-login',
		get_stylesheet_directory_uri() . '/style-login.css',
		array(),
		wp_get_theme()->get( 'Version' )
	);
}
add_action( 'login_enqueue_scripts', 'wppoland_login_stylesheet' );

Example style-login.css:

body.login {
	background-color: #0d1117;
	display: flex;
	align-items: center;
	justify-content: center;
}

.login form {
	background: #161b22;
	border: 1px solid #30363d;
	box-shadow: none;
	border-radius: 8px;
}

.login label {
	color: #c9d1d9;
}

.wp-core-ui .button-primary {
	background: #238636;
	border-color: rgba(27, 31, 35, 0.15);
}

.login #backtoblog a,
.login #nav a {
	color: #c9d1d9;
}

Match contrast to WCAG where editors need it: pale grey on dark grey fails for some users. Test focus outlines on the username, password, and submit controls; removing outlines for “clean design” is a regress.

Language packs and RTL locales still use the same hooks. Avoid hard-coded English strings in CSS content properties.

Background images on body.login should be compressed and sized for a simple full-bleed photo, not a 4K marketing hero. Login traffic is frequent for editors; a heavy background taxes every session. Prefer a solid brand colour or a subtle pattern under 100 KB.

If the brand book mandates a light form on a light background, increase border contrast on .login form so the card still reads as a distinct surface. Many “minimal” logins fail because the form melts into the page.

#Harden login error messages

A failed password for an existing user historically produced a message that confirmed the username. That helps attackers enumerate accounts before brute-forcing passwords.

function wppoland_login_errors() {
	return __( 'Invalid credentials.', 'your-textdomain' );
}
add_filter( 'login_errors', 'wppoland_login_errors' );

Keep the copy boring and identical for unknown user and wrong password paths. This is obscurity for enumeration, not a substitute for:

  1. Disallowing weak admin passwords.
  2. Limiting login attempts at the application or edge.
  3. Turning on 2FA for administrators.
  4. Not using admin as a username on new installs.

Lost-password flows have their own messages. Review those templates if you customise registration or membership plugins that share the login CSS.

Some membership plugins replace wp-login.php with a front-end form. In that case these hooks never fire. Apply the same rules on the plugin’s form hooks, or style their template partials directly. Confirm which URL editors actually use before you declare the job done.

XML-RPC and the REST users endpoint are separate attack surfaces. Branding wp-login.php does nothing for them. Disable or restrict XML-RPC if you do not need it, and keep application passwords under policy if the site uses them.

#Remove the shake animation

Failed logins trigger a shake via wp_shake_js. Some brand guidelines treat it as noise.

function wppoland_remove_login_shake() {
	remove_action( 'login_head', 'wp_shake_js', 12 );
}
add_action( 'login_head', 'wppoland_remove_login_shake' );

If a future core version changes the priority, check the hook in source and adjust. Prefer documenting the intent in a one-line comment above the remove call.

#2FA, passkeys, and plugin fields

Building full 2FA inside functions.php is the wrong project. Use a maintained plugin (for example the official Two Factor plugin or your host’s offering) and style the extra fields so they match the branded form.

Common selectors to restyle after enabling 2FA:

  • .login .backup-methods
  • inputs added for TOTP codes
  • “use a security key” buttons

Load those rules in the same style-login.css. After every plugin update, re-check the login URL on staging: markup class names drift.

Passkeys and WebAuthn UX vary by plugin. Your job on the branding side is contrast, spacing, and not clipping the security-key prompt with overflow: hidden on .login form.

#Child theme vs site plugin

PlacementProsCons
Child themeShips with theme assets and logo pathLost if someone switches themes
Site-specific pluginSurvives theme swapsLogo URL must not hard-code the old theme path
Must-use pluginHard to disable by accidentNeeds deploy access; not ideal for every client

Agencies that hand off the site often prefer a small site plugin named after the client, with the logo in the plugin’s assets/ folder. That keeps login branding when marketing later installs a new block theme.

#What not to do

  1. Do not edit wp-login.php in core. Updates overwrite it.
  2. Do not enqueue heavy frontend bundles on login; keep CSS lean.
  3. Do not hide the login URL and call that “security” while leaving admin / password123 in place.
  4. Do not stack three login-customiser plugins on top of these hooks.
  5. Do not put secrets or API keys in the login CSS file.

Changing the login slug (via a dedicated plugin) is a separate decision. It can cut casual bots; it also breaks bookmarks and some deploy checklists. Branding and slug changes are independent.

If you rename the login path, document the new URL in the handoff package and in the client’s password manager notes. Support tickets that say “login is broken” after a slug change are expensive and avoidable.

#WooCommerce and other form cousins

WooCommerce account pages and checkout login blocks use different markup. Login-screen CSS will not restyle my-account forms. Either accept stock Woo forms or enqueue a separate stylesheet on those templates. Mixing both into style-login.css creates dead rules and confusion for the next developer.

The same split applies to LearnDash, MemberPress, and similar products: identify the real entry URL, then brand that surface. One agency mistake is polishing wp-login.php while customers only ever see a front-end modal that still shows the WordPress logo in an iframe.

#Maintenance after core updates

Major WordPress releases occasionally tweak login markup or default CSS. After updating core on staging:

  1. Diff the login HTML if something looks off.
  2. Re-test logo size; new default heights can clip SVGs.
  3. Confirm remove_action priorities for shake still match.
  4. Re-check 2FA fields if that plugin updated in the same window.

Pin the stylesheet version to the theme or plugin version so browsers do not keep a stale style-login.css after you fix a contrast bug.

#Quick verification checklist

  1. Open /wp-login.php logged out; confirm logo, colours, and home link.
  2. Submit a wrong password; confirm the generic error and no shake if you removed it.
  3. Confirm “Lost your password?” still works and emails still arrive.
  4. With 2FA on, complete a full admin login on staging.
  5. Switch to a second locale if the site is multilingual and re-check layout.
  6. Log in from a mobile viewport; flex centering sometimes collapses the form on short screens.

#Summary

Brand the logo and header URL, enqueue login-only CSS, return generic errors, and optionally drop the shake. Keep the code outside the parent theme. Treat 2FA as a real control and style its UI; treat logo CSS as polish.

Hand the client a one-page note: where the code lives, how to replace the logo file, and which URL is canonical for staff logins. That note prevents the next freelancer from installing a redundant login plugin six months later.

If the install already shows signs of abuse, or you need login hardening reviewed together with plugins and hosting, start from a WordPress security audit rather than stacking another cosmetic login plugin on a compromised site.

Next step

Turn the article into an actual implementation

This block strengthens internal linking and gives readers the most relevant next move instead of leaving them at a dead end.

Related cluster

Explore other WordPress services and knowledge base

Strengthen your business with professional technical support in key areas of the WordPress ecosystem.

How do you change the WordPress login logo without a plugin?#
Hook into login_enqueue_scripts, output CSS for the login screen, and replace the default logo with your own image from the theme or child theme.
Can you customise wp-login.php and improve security at the same time?#
Yes. Brand the page, load dedicated CSS, return generic login errors, and remove distracting UI behaviour without changing the dashboard experience.
Where should WordPress login customisation code live?#
Use a child theme or a small site-specific plugin so the changes survive theme updates and stay isolated from unrelated frontend styling.
Does hiding login errors stop brute force attacks?#
No. Generic errors reduce username enumeration. You still need rate limiting, strong credentials, and preferably 2FA or passkeys on admin accounts.
Will a login CSS plugin conflict with this code?#
Often yes. Two sources styling #login h1 a fight each other. Pick hooks in code or one plugin, not both, and test after every core update.

Need an FAQ tailored to your industry and market? We can build one aligned with your business goals.

Let’s discuss

Related Articles