The part of a prototype login flow that has to be real is rejection. Type the wrong password and you shouldn’t get in. Everything else, from the masked field to the Face ID animation, is decoration by comparison.
That’s the argument of a ProtoPie tutorial on Smashing Magazine, which builds a banking login with validating credentials, a live error state, and a staggered Face ID animation. Its core observation holds up. Participants pause at a login screen that accepts anything and glance up to check they’re doing it right, and from then on every data point you collect is filtered through knowing it’s a demo.
Where the prototype login flow starts lying
The tutorial’s fintech framing is sharp because finance users are trained to notice when something feels off, like a balance that doesn’t add up or a field that accepts anything. Instead of quietly disengaging, they stop mid-session to flag it. You end up with findings about how people behave in a demonstration. It’s a good argument for watching what participants do instead of relying on what they tell you afterward.
WordPress and WooCommerce work has its own version of this. Staging stores often run with a payment gateway in test mode (and one hard-coded card number in the readme), so checkout never fails. Or someone drops a must-use plugin on staging that calls wp_set_current_user( 1 ) for every visitor so the client can see the admin. Both tell the same lie as a login button that always passes, and the mu-plugin variant has a habit of reaching production because it never lived in version control. It’s one of the first things I check when I inherit a site.
The wrong fix is to hide the form
The tempting fix is to prefill the credentials, or skip the screen entirely and drop testers on the dashboard. That fails for the same reason the static screen failed, because the login stops being an interaction and you learn nothing from it. You don’t see what the error state does to people, whether they retry, reach for Face ID, or read the message at all. A working login answers those questions and a bypassed one can’t.
There’s a softer version of this in client demos, where the login works because you typed the password correctly and nobody else has tried it. Stakeholders conclude the flow is finished. Walking that back later costs more than the hour it would have taken to wire real validation in the first place, and the same discipline applies when you test checkout flows for real instead of trusting a test gateway.
Make the validation real, one page deep
The fix that holds up is a thin slice of real code on a page that exists only for the test: a form posting to wp_signon against a seeded demo user. WordPress already has the machinery, which is why I’d rather prototype this in code than in a design tool whenever the target is a WordPress site.
add_action( 'init', 'bbioon_demo_login' );
function bbioon_demo_login() {
if ( ! isset( $_POST['bbioon_login_nonce'] ) ) {
return;
}
check_admin_referer( 'bbioon_login', 'bbioon_login_nonce' );
$user = wp_signon(
array(
'user_login' => sanitize_user( wp_unslash( $_POST['log'] ?? '' ) ),
'user_password' => $_POST['pwd'] ?? '',
'remember' => ! empty( $_POST['rememberme'] ),
)
);
if ( is_wp_error( $user ) ) {
wp_safe_redirect( add_query_arg( 'bbioon_login', 'failed', wp_get_referer() ?: home_url() ) );
exit;
}
wp_safe_redirect( home_url( '/demo-dashboard/' ) );
exit;
}
On the page that renders the form, show the error when the redirect carries the failed flag, and use the copy you intend to ship. Core has a habit worth borrowing here. WordPress runs its login errors through the login_errors filter and keeps the wording vague on purpose, because distinguishing a wrong username from a wrong password tells an attacker which accounts exist. Your error state should respect that constraint instead of inventing copy the production build can’t use. The developer docs on how login and registration work cover the mechanics, and wp_login_form() documents the field defaults if you’d rather render the core form than write your own markup.
For a checkout test, the equivalent of a real error state is wc_add_notice( $message, 'error' ), which stores the notice in the session and prints it after the redirect. The prototype should imitate that behavior, an error that survives navigation. Payment gateways use the same call when validation fails, per the WooCommerce gateway docs.
What I still don’t have a settled answer on is how much fidelity is enough before a prototype quietly becomes a product. The ProtoPie chapter spends much of its effort on a Lottie animation staggered with half-second delays. I suspect that detail earns real trust in moderated sessions, but it costs hours no smaller project will bill for. That choice decides whether you prototype in code or in a tool. If you’re weighing that on a WooCommerce build, or you want the demo login on your staging site replaced with something that validates, send me the URL and I’ll take a look.