Build a Responsive Login Page

Build a responsive login form with HTML, CSS, and JavaScript.

Introduction

30 Second Summary

A sign-in page can look finished while still feeling awkward on a narrow screen. Unclear feedback can also leave someone wondering whether the form worked.

In this project, you will build a responsive login demonstration with HTML, CSS, and JavaScript. Your page will use built-in browser validation before showing safe submission feedback.

What You'll Build

Picture opening your local page in Microsoft Edge to see a centered login card that stays readable as the window narrows. Valid practice details produce a clear success message without sending credentials anywhere.

By the end of this project, you'll have:

  • A local page you can open to demonstrate a responsive login card across wide and narrow browser windows.
  • A form with labeled email and password fields that blocks empty entries and incorrectly formatted email addresses.
  • A safe success message that confirms a practice submission without pretending to authenticate a real account.
  • Secret Mission: Add an accessible Show/Hide password control that updates its label and pressed state.

Are there any prerequisites?

You do not need previous front-end development experience. You only need the installed copies of Visual Studio Code and Microsoft Edge from your Windows environment.

Before We Start

First, let's lock in the boundary of what you're building. This project collects practice input locally in your browser, while real authentication requires a secure server.

Set Up and Open the Starter Page

A local page becomes easier to build when every file lives in one folder you can find again.

Here, you'll prepare a login-page workspace in Visual Studio Code. You'll add a small HTML document that Microsoft Edge can render immediately.

In this step, get ready to:
  • Prepare a trusted login-page workspace in Visual Studio Code.
  • Build the valid index.html document skeleton.
  • Display the starter heading in Microsoft Edge.
Open the project folder in Visual Studio Code

A workspace is the folder Visual Studio Code treats as the home for your project. Opening login-page now keeps every later file together.

  • Press the Windows key to open the search bar.
  • Type Visual Studio Code and press Enter to open it.
  • Click File in the top menu bar.
  • Select Open Folder....

Why open a folder workspace?

The workspace gives Visual Studio Code one place to show every project file. Its Explorer sidebar becomes a map of your login-page folder.

  • Choose your Desktop from the folder picker's navigation pane.
  • Use the folder picker's new-folder control to create a folder named login-page.
  • Select the new login-page folder.
  • Confirm the folder selection.
  • Trust the folder if Workspace Trust appears because you created this folder yourself.

That first piece is in place: Explorer now shows login-page as your workspace.

Folder missing from Explorer?

  • Check that the folder name at the top of Explorer is login-page.
  • Reopen login-page through Open Folder... if another folder is shown.

Help me open the correct folder in Visual Studio Code.

Create the starter HTML document

An HTML document gives the browser a defined page structure. This starter keeps the result small enough to verify immediately.

  • Use the new-file control in the Explorer sidebar to create index.html inside login-page.
  • Paste the following document into index.html:
<!doctype html>
<html lang="en-US">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <title>Login Page</title>
  </head>
  <body>
    <h1>Login page</h1>
  </body>
</html>

What does this HTML do?

  • The <!doctype html> line identifies this file as a modern HTML document.
  • The lang="en-US" value identifies the page's primary language.
  • The utf-8 value gives the browser a character encoding for the page.
  • The width=device-width value prepares the page to match the browser window's width.
  • The page title supplies the text for the browser tab.
  • The <h1>Login page</h1> element provides the visible starter heading.
  • Save index.html.
  • Check that Explorer lists index.html under login-page.

Your first project file is now saved inside the workspace.

File not showing in Explorer?

  • Check that the filename is exactly index.html.
  • Confirm that the file sits directly under the login-page folder in Explorer.
  • Save the file again if its editor tab shows unsaved changes.

Help me find or save index.html in my Visual Studio Code workspace.

✔️ Awesome, I've got everything!

Great. Make sure index.html is saved before opening it in Microsoft Edge.

ⓧ I'd like to double check the full code

Compare your complete index.html file with this version.

<!doctype html>
<html lang="en-US">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <title>Login Page</title>
  </head>
  <body>
    <h1>Login page</h1>
  </body>
</html>
Open the starter page in Microsoft Edge

Microsoft Edge can render index.html directly from your computer. This check proves the browser can find your saved file.

  • Press the Windows key to open the search bar.
  • Type Microsoft Edge and press Enter to open it.

Before you open the file, make your prediction about the heading Microsoft Edge will render:

  • Open the file picker by pressing Ctrl+O.
  • Select your Desktop in the file picker.
  • Open the login-page folder.
  • Select index.html.
  • Confirm the file selection.
  • Keep this Microsoft Edge tab open for later refreshes.

You'll see the heading Login page on a plain white page.

You have a working browser result: Microsoft Edge is rendering the saved file from your login-page workspace.

Starter heading not visible?

  • Confirm that Microsoft Edge opened index.html from the login-page folder.
  • Return to Visual Studio Code to confirm <h1>Login page</h1> is inside the document body.
  • Save index.html before opening the file again.

Help me work out why Microsoft Edge is not showing my Login page heading.

Your local starter page is open in Microsoft Edge. Next, you'll turn that single heading into a semantic HTML form.

Build the Semantic Login Form

Your starter heading now opens from a local file in Microsoft Edge.

A heading cannot collect an email address or password. In this step, you will replace it with a semantic HTML form that gives every control a clear purpose.

In this step, get ready to:
  • Build the page shell for the login interface.
  • Add explicitly labeled account fields.
  • Complete the form with a remember-me option plus an inert sign-in button.
Build the page shell

The page shell groups the login interface as the main content. Its section uses the login heading as a clear label for the card.

  • Switch back to Visual Studio Code.
  • Select index.html in the Explorer sidebar.
  • Select everything from <body> through </body>.
  • Replace the selected body with this page shell:
  <body>
    <main class="page-shell">
      <section class="login-card" aria-labelledby="login-title">
        <h1 id="login-title">Welcome back</h1>
        <p class="subtitle">Sign in to continue to your account.</p>

        <p class="demo-note">
          This front-end demo validates the form in your browser. It does not
          authenticate an account.
        </p>
      </section>
    </main>
  </body>

What does this structure do?

  • The main element identifies the page's primary content.
  • The section element groups the login interface into one card.
  • The heading gives the section an accessible label through its matching identifier.
  • The demo note sets the security boundary for this front-end practice page.
  • Save index.html by pressing Ctrl+S.
  • Switch back to Microsoft Edge.
  • Refresh the page by pressing Ctrl+R.

You should see the heading Welcome back with supporting text plus the front-end demo note.

Still seeing the starter heading?

Confirm that you replaced the complete body in index.html. Check that the file tab no longer shows an unsaved-change marker.

If Microsoft Edge still shows Login page, confirm that its current local file comes from the login-page folder.

Help me find why my updated page shell is missing.

Add the labeled account fields

An explicit label connects its text to a form control when the label's for value matches the control's id. This makes each field's purpose clear.

  • Return to index.html in Visual Studio Code.
  • Find the subtitle ending with Sign in to continue to your account..
  • Insert this email form immediately below the subtitle:
        <form id="login-form" method="post">
          <div class="form-field">
            <label for="email">Email address</label>
            <input
              id="email"
              name="email"
              type="email"
              autocomplete="username"
              placeholder="name@example.com"
            >
          </div>
        </form>

How does the email field work?

  • The matching for="email" plus id="email" values associate the label with its input.
  • The email input type prepares the browser to check email formatting once submission is enabled.
  • The username autocomplete value identifies this field as the account name for a sign-in form.
  • The post method keeps form values out of the page URL if the form is submitted later.
  • Save index.html.
  • Switch back to Microsoft Edge.
  • Refresh the page by pressing Ctrl+R.

You should see an Email address label above a text field containing the name@example.com placeholder.

Email field missing?

Check that the new form sits between the subtitle plus the demo note. Confirm that every opening tag has its matching closing tag.

Help me debug the missing email field.

The password field uses the same label association. Its input type obscures the entered text while the autocomplete value identifies it as the current password for the account.

  • Find the closing </div> immediately before </form>.
  • Insert this password field after that closing tag:
          <div class="form-field">
            <label for="password">Password</label>
            <input
              id="password"
              name="password"
              type="password"
              autocomplete="current-password"
            >
          </div>

How does the password field work?

  • The matching for="password" plus id="password" values associate the label with its input.
  • The password input type obscures entered text from casual observation.
  • The current-password autocomplete value tells the browser what kind of account value belongs in this field.
  • Save index.html.
  • Switch back to Microsoft Edge.
  • Refresh the page by pressing Ctrl+R.

You should see the password field directly below the email field. Any text you type into it appears obscured.

Password field in the wrong place?

Confirm that the password field is inside login-form. Its complete form-field block should sit before </form>.

Help me correct the password field structure.

Complete and inspect the form

The final controls let someone remember their preference or attempt to sign in. The temporary button uses the button type with no default submission behavior.

  • Find the closing </form> tag in index.html.
  • Insert these final controls immediately above that tag:
          <div class="form-options">
            <input id="remember" name="remember" type="checkbox">
            <label for="remember">Remember me</label>
          </div>

          <button class="sign-in-button" type="button">Sign in</button>

What do these controls add?

  • The matching remember values associate the checkbox with its visible label.
  • The sign-in-button class gives the button a styling hook for the next step.
  • The temporary button type keeps this practice form from submitting.
  • Save index.html.

Before you refresh, do you think semantic structure alone produces a finished visual design?

  • Switch back to Microsoft Edge.
  • Refresh the page by pressing Ctrl+R.
  • Narrow the Microsoft Edge window.

You should see labeled email plus password fields followed by a remember-me checkbox and a Sign in button. The controls appear in a vertical form with Microsoft Edge's default styling.

That plain result is intentional. You now have the complete semantic structure before any visual presentation is added.

Missing one of the controls?

Check that the email field, password field, checkbox, plus button all sit between the form's opening tag and closing tag. Confirm that the file is saved before refreshing Microsoft Edge.

Help me compare my form with the expected structure.

✔️ Awesome, I've got everything!

Great. Your local page now collects practice input through a complete semantic form.

ⓧ I'd like to double check the full code

Compare your saved index.html with this complete version.

<!doctype html>
<html lang="en-US">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <title>Login Page</title>
  </head>
  <body>
    <main class="page-shell">
      <section class="login-card" aria-labelledby="login-title">
        <h1 id="login-title">Welcome back</h1>
        <p class="subtitle">Sign in to continue to your account.</p>

        <form id="login-form" method="post">
          <div class="form-field">
            <label for="email">Email address</label>
            <input
              id="email"
              name="email"
              type="email"
              autocomplete="username"
              placeholder="name@example.com"
            >
          </div>

          <div class="form-field">
            <label for="password">Password</label>
            <input
              id="password"
              name="password"
              type="password"
              autocomplete="current-password"
            >
          </div>

          <div class="form-options">
            <input id="remember" name="remember" type="checkbox">
            <label for="remember">Remember me</label>
          </div>

          <button class="sign-in-button" type="button">Sign in</button>
        </form>

        <p class="demo-note">
          This front-end demo validates the form in your browser. It does not
          authenticate an account.
        </p>
      </section>
    </main>
  </body>
</html>

Your browser now shows every control the login interface needs. Next, you will turn its plain layout into a centered responsive card.

Style the Responsive Login Card

Your semantic HTML form now gives every control a clear purpose. However, its browser-default appearance in Microsoft Edge makes the page harder to scan.

CSS turns that raw form into a centered card with clear spacing. Responsive design keeps the same card readable when the browser window becomes narrow.

In this step, get ready to:
  • Connect the page to a new stylesheet.
  • Style the card and its form controls.
  • Adapt the layout for narrow browser windows.
Connect the stylesheet

A separate stylesheet keeps the page structure in index.html while styles.css controls its appearance. The browser connects the files through a stylesheet link in the document head.

  • In the Visual Studio Code Explorer sidebar, select the login-page folder.
  • Use the new-file control beside the folder to create styles.css.
  • Confirm Explorer lists styles.css beside index.html.
  • Switch back to index.html.
  • Find the <title>Login Page</title> line.
  • Connect the new stylesheet by adding this line directly below it:
    <link rel="stylesheet" href="styles.css">

What does this link do?

The link element tells the browser to load styles.css when it reads index.html.

The finished card also uses a small decorative brand mark above its heading.

  • Find the opening <section class="login-card" aria-labelledby="login-title"> line.
  • Add the brand mark directly below that line:
        <div class="brand-mark" aria-hidden="true">L</div>

Why hide the brand mark?

The letter is decorative because the login heading already identifies the card. The aria-hidden="true" attribute keeps assistive technology focused on meaningful content.

  • Save index.html by pressing Ctrl+S.
  • Return to the local page in Microsoft Edge.
  • Refresh the page by pressing Ctrl+R.

You should see the letter L above the heading. It still appears as plain text because the stylesheet is ready for its visual rules.

Don't see the brand mark?

Check that the brand mark sits inside .login-card directly above #login-title.

Confirm that both files are saved inside the same login-page folder.

Help me check why my brand mark is missing from the login page.

✔️ Awesome, I've got everything!

Great. Make sure index.html is saved before styling the page.

ⓧ I'd like to double check the full code

Compare your index.html with this complete version:

<!doctype html>
<html lang="en-US">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <title>Login Page</title>
    <link rel="stylesheet" href="styles.css">
  </head>
  <body>
    <main class="page-shell">
      <section class="login-card" aria-labelledby="login-title">
        <div class="brand-mark" aria-hidden="true">L</div>
        <h1 id="login-title">Welcome back</h1>
        <p class="subtitle">Sign in to continue to your account.</p>

        <form id="login-form" method="post">
          <div class="form-field">
            <label for="email">Email address</label>
            <input id="email" name="email" type="email" autocomplete="username" placeholder="name@example.com">
          </div>

          <div class="form-field">
            <label for="password">Password</label>
            <input id="password" name="password" type="password" autocomplete="current-password">
          </div>

          <div class="form-options">
            <input id="remember" name="remember" type="checkbox">
            <label for="remember">Remember me</label>
          </div>

          <button class="sign-in-button" type="button">Sign in</button>
        </form>

        <p class="demo-note">
          This front-end demo validates the form in your browser. It does not
          authenticate an account.
        </p>
      </section>
    </main>
  </body>
</html>
Style the card and controls

The stylesheet starts with shared rules for sizing and typography. These rules create a consistent canvas before individual parts of the card receive their own styles.

  • Switch to styles.css.
  • Create the page foundation by adding this code:
* {
  box-sizing: border-box;
}

:root {
  font-family: Arial, Helvetica, sans-serif;
  color: #172033;
  background: #eef2ff;
}

body {
  min-height: 100vh;
  margin: 0;
}

button,
input {
  font: inherit;
}

What do these foundation styles do?

  • The universal selector makes borders and padding count inside each element's declared size.
  • The :root rules set the page font and default colours.
  • The body rules remove the browser margin and let the page fill the viewport.
  • The controls inherit the same font as the rest of the page.
  • Save styles.css.
  • Return to Microsoft Edge.
  • Refresh the page with Ctrl+R.

You should see a pale background with darker text in the new font stack.

Still seeing the default page?

Check that the file is named exactly styles.css. Confirm that the stylesheet link uses the same spelling.

Help me find why styles.css is not changing my local page.

The page shell uses a grid to place one item in the center of the viewport. The card can fill available space while its width remains capped at 420px.

  • Return to styles.css.
  • Center the card by adding these layout rules below the existing styles:
.page-shell {
  display: grid;
  min-height: 100vh;
  place-items: center;
  padding: 24px;
  background:
    radial-gradient(circle at top left, #c7d2fe, transparent 38%),
    linear-gradient(135deg, #eef2ff, #f8fafc);
}

.login-card {
  width: min(100%, 420px);
  padding: 36px;
  border: 1px solid #dbe3f0;
  border-radius: 20px;
  background: #ffffff;
  box-shadow: 0 24px 60px rgb(30 41 59 / 0.16);
}

How does the card stay responsive?

  • The .page-shell grid centers the card across the full viewport.
  • The width: min(100%, 420px) rule uses the available width until it reaches the cap.
  • The border and shadow separate the white card from the gradient background.
  • Save styles.css.
  • Return to Microsoft Edge.
  • Refresh the page with Ctrl+R.

You should see a white card centered over a soft gradient. Its width should stop growing at 420px.

Card not centered?

Confirm that the selectors are spelled .page-shell and .login-card. They must match the classes in index.html.

Help me debug why my login card is not centered.

Typography creates a clear reading order inside the card. The brand mark adds a visual anchor without competing with the login heading.

  • Return to styles.css.
  • Style the brand and heading area by adding:
.brand-mark {
  display: grid;
  width: 48px;
  height: 48px;
  margin-bottom: 24px;
  place-items: center;
  border-radius: 14px;
  color: #ffffff;
  background: #4f46e5;
  font-size: 1.25rem;
  font-weight: 700;
}

h1 {
  margin: 0;
  font-size: 2rem;
  line-height: 1.2;
}

.subtitle {
  margin: 8px 0 28px;
  color: #64748b;
}

What changes in the card header?

  • The .brand-mark grid centers the letter inside a purple rounded square.
  • The heading removes its default margin and uses a larger size.
  • The subtitle uses softer text with deliberate space below it.
  • Save styles.css.
  • Return to Microsoft Edge.
  • Refresh the page with Ctrl+R.

You should see the L centered inside a purple tile. The heading and subtitle should now form a compact header.

Brand mark still looks plain?

Check that the CSS selector includes the leading full stop in .brand-mark.

Help me fix the styling for my .brand-mark element.

Grid spacing makes the form easier to scan. Consistent input dimensions also give the email and password controls the same visual weight.

  • Return to styles.css.
  • Style the form layout and text inputs by adding:
#login-form {
  display: grid;
  gap: 20px;
}

.form-field {
  display: grid;
  gap: 8px;
}

.form-field label {
  font-size: 0.95rem;
  font-weight: 700;
}

.form-field input {
  width: 100%;
  min-height: 48px;
  padding: 12px 14px;
  border: 1px solid #cbd5e1;
  border-radius: 10px;
  color: #172033;
  background: #ffffff;
}

.form-field input:hover {
  border-color: #94a3b8;
}

How are the fields organised?

  • The #login-form grid keeps a consistent gap between each form section.
  • Each .form-field places its label above its input.
  • The inputs fill the card width and provide a large typing area.
  • The hover rule darkens an input border when the pointer moves over it.
  • Save styles.css.
  • Return to Microsoft Edge.
  • Refresh the page with Ctrl+R.

You should see evenly spaced labels and full-width rounded inputs inside the card.

Inputs still look unstyled?

Confirm each input remains inside an element with the form-field class.

Help me check why my .form-field input styles are not appearing.

A visible focus state shows keyboard users which control currently receives input. The remember-me row uses a horizontal layout so its checkbox stays beside its label.

  • Return to styles.css.
  • Add the focus and checkbox styles below the input rules:
.form-field input:focus-visible,
.sign-in-button:focus-visible {
  border-color: #4f46e5;
  outline: 3px solid rgb(79 70 229 / 0.25);
  outline-offset: 2px;
}

.form-options {
  display: flex;
  align-items: center;
  gap: 8px;
  color: #475569;
  font-size: 0.9rem;
}

.form-options input {
  width: 18px;
  height: 18px;
  accent-color: #4f46e5;
}

Why use a focus outline?

The focus outline makes keyboard position visible around text fields and the sign-in button. Its offset keeps the outline separate from the control border.

The checkbox uses the same purple accent as the rest of the interface. This keeps the control visually connected to the card.

  • Save styles.css.
  • Return to Microsoft Edge.
  • Refresh the page with Ctrl+R.
  • Press Tab until the email input receives focus.

You should see a purple outline around the focused input. The checkbox should sit neatly beside its label.

Can't see the focus outline?

Use the Tab key to move focus instead of clicking the field. The :focus-visible selector responds to keyboard focus.

Help me debug why my :focus-visible styles are not appearing.

The primary button needs enough contrast and height to stand out as the form's main action. Pointer states provide visual feedback when someone hovers over it or presses it.

  • Return to styles.css.
  • Style the sign-in button by adding:
.sign-in-button {
  min-height: 48px;
  border: 0;
  border-radius: 10px;
  color: #ffffff;
  background: #4f46e5;
  font-weight: 700;
  cursor: pointer;
}

.sign-in-button:hover {
  background: #4338ca;
}

.sign-in-button:active {
  transform: translateY(1px);
}

How does the button respond?

  • The base rule creates a full-width purple action with rounded corners.
  • The hover rule darkens the background under the pointer.
  • The active rule moves the button down by one pixel while it is pressed.
  • Save styles.css.
  • Return to Microsoft Edge.
  • Refresh the page with Ctrl+R.
  • Move your pointer over the Sign in button.

You should see the button become a darker shade of purple while the pointer is over it.

Button still looks plain?

Confirm that the button in index.html still has the sign-in-button class.

Help me fix the missing .sign-in-button styles.

The final card details define a space for future form feedback and separate the demo note from the controls. The divider keeps the security boundary visible without distracting from the form.

  • Return to styles.css.
  • Add the message and demo-note styles at the bottom of the file:
.form-message {
  min-height: 1.5rem;
  margin: 0;
  color: #166534;
  font-size: 0.9rem;
  font-weight: 700;
  visibility: hidden;
}

.form-message.is-visible {
  visibility: visible;
}

.demo-note {
  margin: 24px 0 0;
  padding-top: 20px;
  border-top: 1px solid #e2e8f0;
  color: #64748b;
  font-size: 0.82rem;
  line-height: 1.5;
}

What do these finishing styles prepare?

The .form-message styles reserve a predictable area for the submission feedback added in the next step. The is-visible class controls when that message can be seen.

The .demo-note styles add a divider and smaller supporting text beneath the form.

  • Save styles.css.
  • Return to Microsoft Edge.
  • Refresh the page with Ctrl+R.

You should see a thin divider above the smaller demo note at the bottom of the card.

Demo note missing its divider?

Check that the paragraph in index.html has the demo-note class. Confirm that the .demo-note rule has a leading full stop.

Help me debug the missing .demo-note styles.

Add the responsive layout and test it

A media query applies a different set of rules when the viewport matches a condition. This one reduces spacing below 480px so the card retains more room for its content.

  • Return to styles.css.
  • Add the narrow-screen rules at the bottom of the file:
@media (max-width: 480px) {
  .page-shell {
    padding: 12px;
  }

  .login-card {
    padding: 24px;
    border-radius: 16px;
  }

  h1 {
    font-size: 1.7rem;
  }
}

How does the media query help?

  • The page shell uses less outer padding on narrow screens.
  • The card uses less inner padding and a smaller corner radius.
  • The heading becomes slightly smaller so it fits the reduced space.
  • Save styles.css.
  • Return to Microsoft Edge.
  • Refresh the page with Ctrl+R.

Before you resize the window, how do you expect the card spacing to change once the viewport becomes narrower than 480px?

  • Drag one side of the Microsoft Edge window inward until the page becomes narrow.

You should see the card remain centered and readable. Its outer spacing, inner padding, corner radius, and heading size should become smaller.

Before you click the styled button, do you expect visual styling alone to create a submission response?

  • Click the Sign in button.

You should see no message and no page change. That shortfall is intentional because the button still has type="button" and no interaction code.

Narrow layout not changing?

Confirm that the @media block sits after the other styles. Check that every opening brace has a matching closing brace.

Help me debug why my responsive CSS rules are not applying below 480px.

✔️ Awesome, I've got everything!

Your responsive card is complete. Make sure styles.css is saved before moving on.

ⓧ I'd like to double check the full code

Compare your styles.css with this complete version:

* {
  box-sizing: border-box;
}

:root {
  font-family: Arial, Helvetica, sans-serif;
  color: #172033;
  background: #eef2ff;
}

body {
  min-height: 100vh;
  margin: 0;
}

button,
input {
  font: inherit;
}

.page-shell {
  display: grid;
  min-height: 100vh;
  place-items: center;
  padding: 24px;
  background:
    radial-gradient(circle at top left, #c7d2fe, transparent 38%),
    linear-gradient(135deg, #eef2ff, #f8fafc);
}

.login-card {
  width: min(100%, 420px);
  padding: 36px;
  border: 1px solid #dbe3f0;
  border-radius: 20px;
  background: #ffffff;
  box-shadow: 0 24px 60px rgb(30 41 59 / 0.16);
}

.brand-mark {
  display: grid;
  width: 48px;
  height: 48px;
  margin-bottom: 24px;
  place-items: center;
  border-radius: 14px;
  color: #ffffff;
  background: #4f46e5;
  font-size: 1.25rem;
  font-weight: 700;
}

h1 {
  margin: 0;
  font-size: 2rem;
  line-height: 1.2;
}

.subtitle {
  margin: 8px 0 28px;
  color: #64748b;
}

#login-form {
  display: grid;
  gap: 20px;
}

.form-field {
  display: grid;
  gap: 8px;
}

.form-field label {
  font-size: 0.95rem;
  font-weight: 700;
}

.form-field input {
  width: 100%;
  min-height: 48px;
  padding: 12px 14px;
  border: 1px solid #cbd5e1;
  border-radius: 10px;
  color: #172033;
  background: #ffffff;
}

.form-field input:hover {
  border-color: #94a3b8;
}

.form-field input:focus-visible,
.sign-in-button:focus-visible {
  border-color: #4f46e5;
  outline: 3px solid rgb(79 70 229 / 0.25);
  outline-offset: 2px;
}

.form-options {
  display: flex;
  align-items: center;
  gap: 8px;
  color: #475569;
  font-size: 0.9rem;
}

.form-options input {
  width: 18px;
  height: 18px;
  accent-color: #4f46e5;
}

.sign-in-button {
  min-height: 48px;
  border: 0;
  border-radius: 10px;
  color: #ffffff;
  background: #4f46e5;
  font-weight: 700;
  cursor: pointer;
}

.sign-in-button:hover {
  background: #4338ca;
}

.sign-in-button:active {
  transform: translateY(1px);
}

.form-message {
  min-height: 1.5rem;
  margin: 0;
  color: #166534;
  font-size: 0.9rem;
  font-weight: 700;
  visibility: hidden;
}

.form-message.is-visible {
  visibility: visible;
}

.demo-note {
  margin: 24px 0 0;
  padding-top: 20px;
  border-top: 1px solid #e2e8f0;
  color: #64748b;
  font-size: 0.82rem;
  line-height: 1.5;
}

@media (max-width: 480px) {
  .page-shell {
    padding: 12px;
  }

  .login-card {
    padding: 24px;
    border-radius: 16px;
  }

  h1 {
    font-size: 1.7rem;
  }
}

That is the raw-form problem solved. Next up, you will turn the polished but silent button into a validated demo interaction with visible feedback.

Add Validation and Demo Feedback

Your polished card is already responsive in Microsoft Edge. Its Sign in button still cannot respond when someone uses the form.

This step activates native HTML form validation. A small JavaScript listener then displays safe feedback without sending the practice values anywhere.

In this step, get ready to:
  • Require valid practice entries with browser validation.
  • Connect form submission to JavaScript feedback.
  • Verify every supported form state with fake values.
Enable browser validation

The required attribute makes each field mandatory. The email input also checks whether its value follows an email address format before the browser allows submission.

  • Return to the index.html tab in Visual Studio Code.
  • Find the email input under the Email address label.
  • Find the password input under the Password label.
  • Update both input elements by matching them to the first two elements below.
  • Replace the current Sign in button with the button shown below.
  • Add the final paragraph immediately below the Sign in button by copying this reference:
            <input
              id="email"
              name="email"
              type="email"
              autocomplete="username"
              placeholder="name@example.com"
              required
            >

            <input
              id="password"
              name="password"
              type="password"
              autocomplete="current-password"
              required
            >

          <button class="sign-in-button" type="submit">Sign in</button>

          <p
            id="form-message"
            class="form-message"
            aria-live="polite"
          ></p>

What changes here?

  • The two required attributes stop empty submissions.
  • The email field keeps type="email" so the browser can reject incorrectly formatted addresses.
  • The type="submit" value makes the button start the form submission process.
  • The aria-live="polite" region gives assistive technology a place to announce the later status update.
  • Save index.html by pressing Ctrl+S.
  • Switch back to the local page in Microsoft Edge.

Before you try the empty form, which field do you think Microsoft Edge will stop at first?

  • Refresh the page by pressing Ctrl+R.
  • Clear both form fields.
  • Click Sign in.

Microsoft Edge blocks the submission. You will see its validation prompt on the first required field.

  • Enter not-an-email in the Email address field.
  • Enter practice-only in the Password field.
  • Click Sign in again.

The browser blocks this submission because the email value has an invalid format. Your form now catches two common input problems before JavaScript runs.

Form submission is not being blocked?

Check that both text fields contain the required attribute. Confirm that the email field still uses type="email".

Check that the Sign in button uses type="submit".

Help me check why browser validation is not blocking my login form submission.

Connect the submit event

A DOM event lets JavaScript respond when a valid form reaches submission. The listener prevents navigation before it reveals the status message inside the card.

  • Return to Visual Studio Code.
  • Right-click the login-page folder in the Explorer sidebar.
  • Select New File.
  • Enter script.js as the file name.
  • Paste this code into script.js:
const loginForm = document.getElementById("login-form");
const formMessage = document.getElementById("form-message");

loginForm.addEventListener("submit", function (event) {
  event.preventDefault();
  formMessage.textContent =
    "Form submitted successfully. This demo does not sign in to a real account.";
  formMessage.classList.add("is-visible");
});

What does this code do?

  • The first two constants store references to the form and its status message.
  • The submit listener runs only after browser validation accepts the field values.
  • The event.preventDefault() call keeps the local page from navigating away.
  • The textContent assignment creates the safe demonstration message.
  • The classList.add("is-visible") call reveals that message using the existing CSS.
  • Save script.js by pressing Ctrl+S.
  • Return to the index.html tab.
  • Find the closing </body> tag near the bottom of the file.
  • Load script.js by adding this line immediately above the closing body tag:
    <script src="script.js"></script>

Why load the script here?

The browser reads the form before it runs script.js. This gives document.getElementById() access to the elements that the listener needs.

  • Save index.html by pressing Ctrl+S.
  • Switch back to the local page in Microsoft Edge.

Before you submit valid practice values, do you think the page will navigate or display feedback inside the card?

  • Refresh the page by pressing Ctrl+R.
  • Enter learner@example.com in the Email address field.
  • Enter practice-only in the Password field.
  • Click Sign in.

You will stay on the local page. The card will show Form submitted successfully. This demo does not sign in to a real account.

Success message is not appearing?

Confirm that script.js appears beside index.html in the Explorer. Check that the script tag spells the file name exactly.

Confirm that the form uses id="login-form". Check that the message uses id="form-message".

Help me find why my valid login demo submission does not display the success message.

Test each form state

Keep Test Data Fictional

Real credentials do not belong in this practice page. The fake values below keep your test inside the demo's front-end boundary.

Before you run all three checks, which submissions do you expect to reach the JavaScript success message?

  • Refresh the page by pressing Ctrl+R.
  • Clear both form fields.
  • Click Sign in.

Microsoft Edge blocks the empty submission. The JavaScript success message stays hidden.

  • Enter not-an-email in the Email address field.
  • Enter practice-only in the Password field.
  • Click Sign in.

Microsoft Edge blocks the incorrectly formatted email. The JavaScript success message remains hidden.

  • Replace the email value with learner@example.com.
  • Click Sign in.

You will see Form submitted successfully. This demo does not sign in to a real account. The page stays on the local file.

You have completed the full interaction loop. Your browser now blocks invalid entries before JavaScript displays safe feedback for valid practice data.

✔️ Awesome, I've got everything!

Great work. Make sure index.html and script.js are saved before finishing this step.

ⓧ I'd like to double check the full code

The complete saved files should match these references:

<!doctype html>
<html lang="en-US">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <title>Login Page</title>
    <link rel="stylesheet" href="styles.css">
  </head>
  <body>
    <main class="page-shell">
      <section class="login-card" aria-labelledby="login-title">
        <div class="brand-mark" aria-hidden="true">L</div>
        <h1 id="login-title">Welcome back</h1>
        <p class="subtitle">Sign in to continue to your account.</p>

        <form id="login-form" method="post">
          <div class="form-field">
            <label for="email">Email address</label>
            <input
              id="email"
              name="email"
              type="email"
              autocomplete="username"
              placeholder="name@example.com"
              required
            >
          </div>

          <div class="form-field">
            <label for="password">Password</label>
            <input
              id="password"
              name="password"
              type="password"
              autocomplete="current-password"
              required
            >
          </div>

          <div class="form-options">
            <input id="remember" name="remember" type="checkbox">
            <label for="remember">Remember me</label>
          </div>

          <button class="sign-in-button" type="submit">Sign in</button>

          <p
            id="form-message"
            class="form-message"
            aria-live="polite"
          ></p>
        </form>

        <p class="demo-note">
          This front-end demo validates the form in your browser. It does not
          authenticate an account.
        </p>
      </section>
    </main>

    <script src="script.js"></script>
  </body>
</html>
* {
  box-sizing: border-box;
}

:root {
  font-family: Arial, Helvetica, sans-serif;
  color: #172033;
  background: #eef2ff;
}

body {
  min-height: 100vh;
  margin: 0;
}

button,
input {
  font: inherit;
}

.page-shell {
  display: grid;
  min-height: 100vh;
  place-items: center;
  padding: 24px;
  background:
    radial-gradient(circle at top left, #c7d2fe, transparent 38%),
    linear-gradient(135deg, #eef2ff, #f8fafc);
}

.login-card {
  width: min(100%, 420px);
  padding: 36px;
  border: 1px solid #dbe3f0;
  border-radius: 20px;
  background: #ffffff;
  box-shadow: 0 24px 60px rgb(30 41 59 / 0.16);
}

.brand-mark {
  display: grid;
  width: 48px;
  height: 48px;
  margin-bottom: 24px;
  place-items: center;
  border-radius: 14px;
  color: #ffffff;
  background: #4f46e5;
  font-size: 1.25rem;
  font-weight: 700;
}

h1 {
  margin: 0;
  font-size: 2rem;
  line-height: 1.2;
}

.subtitle {
  margin: 8px 0 28px;
  color: #64748b;
}

#login-form {
  display: grid;
  gap: 20px;
}

.form-field {
  display: grid;
  gap: 8px;
}

.form-field label {
  font-size: 0.95rem;
  font-weight: 700;
}

.form-field input {
  width: 100%;
  min-height: 48px;
  padding: 12px 14px;
  border: 1px solid #cbd5e1;
  border-radius: 10px;
  color: #172033;
  background: #ffffff;
}

.form-field input:hover {
  border-color: #94a3b8;
}

.form-field input:focus-visible,
.sign-in-button:focus-visible {
  border-color: #4f46e5;
  outline: 3px solid rgb(79 70 229 / 0.25);
  outline-offset: 2px;
}

.form-options {
  display: flex;
  align-items: center;
  gap: 8px;
  color: #475569;
  font-size: 0.9rem;
}

.form-options input {
  width: 18px;
  height: 18px;
  accent-color: #4f46e5;
}

.sign-in-button {
  min-height: 48px;
  border: 0;
  border-radius: 10px;
  color: #ffffff;
  background: #4f46e5;
  font-weight: 700;
  cursor: pointer;
}

.sign-in-button:hover {
  background: #4338ca;
}

.sign-in-button:active {
  transform: translateY(1px);
}

.form-message {
  min-height: 1.5rem;
  margin: 0;
  color: #166534;
  font-size: 0.9rem;
  font-weight: 700;
  visibility: hidden;
}

.form-message.is-visible {
  visibility: visible;
}

.demo-note {
  margin: 24px 0 0;
  padding-top: 20px;
  border-top: 1px solid #e2e8f0;
  color: #64748b;
  font-size: 0.82rem;
  line-height: 1.5;
}

@media (max-width: 480px) {
  .page-shell {
    padding: 12px;
  }

  .login-card {
    padding: 24px;
    border-radius: 16px;
  }

  h1 {
    font-size: 1.7rem;
  }
}
const loginForm = document.getElementById("login-form");
const formMessage = document.getElementById("form-message");

loginForm.addEventListener("submit", function (event) {
  event.preventDefault();
  formMessage.textContent =
    "Form submitted successfully. This demo does not sign in to a real account.";
  formMessage.classList.add("is-visible");
});

Secret mission

Add a Show/Hide Password Control

Add a password visibility control that helps people check what they typed. The button will switch its label and accessible pressed state whenever the password moves between hidden and visible text.

Clean Up Your Resources

Clean Up Your Resources

Choose whether to keep your local project, close it for now, or delete it entirely. The files create no service charges or ongoing costs.

Resources you used:

  • Local login-page folder containing index.html, styles.css, and script.js.

Keep everything running

No action is needed. Choose this option if you want to keep testing or extending your login page.

  • Leave the login-page folder in its current location.
  • Keep the workspace available in Visual Studio Code for your next editing session.
  • Refresh index.html in Microsoft Edge whenever you want to test another change.

Pause - I'll come back to this later

This option closes your editing session while preserving every project file. No process needs to remain running.

  • Save any open changes in Visual Studio Code.
  • Close Visual Studio Code.
  • Close the local index.html tab in Microsoft Edge.
  • Return later by opening the existing login-page folder in Visual Studio Code.

Delete - I don't want to use this again

Deleting the folder removes the only project copy from its current location. Choose this option when you are comfortable rebuilding the login page from scratch.

  • Close the local index.html tab in Microsoft Edge.
  • Close Visual Studio Code.
  • Press the Windows key to open search.
  • Type File Explorer.
  • Press Enter to open Windows File Explorer.
  • Browse to the location containing the login-page folder.
  • Select the login-page folder.
  • Press Delete to remove the folder from its current location.

You should no longer see the login-page folder in that location. Your local project has now been removed.

Nice Work!

Nice Work!

You did it. Your responsive login demo now gives safe feedback after validating practice input without claiming to authenticate a real account.

You've learned how to:

  • Built a semantic HTML form with explicitly associated labels for every control.
  • Styled the plain form as a responsive CSS card that remains readable at different browser widths.
  • Used browser validation to block missing or incorrectly formatted values. Added safe JavaScript feedback without sending practice credentials.
  • Secret Mission: added an accessible Show/Hide password control with synchronized masking, button text, and pressed state.

Ready to quiz yourself?