Skip to content

Building the Complete STO Contact Form

This lesson is the integration point for everything in Module 06. The previous five lessons covered each technique in isolation — resetting browser defaults, styling inputs, customizing buttons and selects, laying out the form with Flexbox, and adding state feedback. Now you put it all together in one complete CSS section for the STO contact form.

Before writing the CSS, confirm your contact.html has this structure. The class names must match exactly — the CSS targets them precisely.

<section class="contact-section">
<div class="content-wrapper">
<h1>Contact Us</h1>
<form class="contact-form" action="#" method="post">
<div class="form-group">
<label for="name">Name</label>
<input type="text" id="name" name="name" placeholder="Your name" required>
</div>
<div class="form-group">
<label for="email">Email</label>
<input type="email" id="email" name="email" placeholder="you@example.com" required>
</div>
<div class="form-group">
<label for="tour">Interested in</label>
<select id="tour" name="tour">
<option value="">Select a tour...</option>
<option value="alpine">Alpine Meadows Trek</option>
<option value="rainforest">Olympic Rainforest Trail</option>
</select>
</div>
<div class="form-group">
<label for="message">Message</label>
<textarea id="message" name="message" rows="5"
placeholder="Tell us about your group..."></textarea>
</div>
<div class="form-submit">
<button type="submit" class="btn btn-primary">Send Message</button>
</div>
</form>
</div>
</section>

Add this entire block to your style.css, organized as a single contact form section:

/* ===========================
Contact Form
=========================== */
.contact-section {
padding: 5rem 0;
}
/* Form layout */
.contact-form {
display: flex;
flex-direction: column;
gap: 1.5rem;
max-width: 640px;
}
/* Form group: label stacked above input */
.form-group {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.form-group label {
font-weight: 600;
font-size: 0.9rem;
color: #1a1a1a;
}
/* All interactive controls */
.form-group input,
.form-group textarea,
.form-group select {
width: 100%;
padding: 0.75rem 1rem;
border: 1px solid #d8d2c8;
border-radius: 4px;
background-color: #ffffff;
color: #1a1a1a;
font: inherit;
font-size: 1rem;
transition: border-color 150ms ease, box-shadow 150ms ease;
}
/* Hover */
.form-group input:hover,
.form-group textarea:hover,
.form-group select:hover {
border-color: #5a5a5a;
}
/* Focus — replaces browser outline */
.form-group input:focus,
.form-group textarea:focus,
.form-group select:focus {
outline: none;
border-color: #2c4a1e;
box-shadow: 0 0 0 3px rgba(44, 74, 30, 0.2);
}
/* Validation error after user interaction */
.form-group input:invalid:not(:placeholder-shown),
.form-group textarea:invalid:not(:placeholder-shown) {
border-color: #c0392b;
box-shadow: 0 0 0 3px rgba(192, 57, 43, 0.15);
}
/* Disabled */
.form-group input:disabled,
.form-group textarea:disabled,
.form-group select:disabled {
opacity: 0.5;
cursor: not-allowed;
background-color: #eae4da;
}
/* Placeholder text */
.form-group input::placeholder,
.form-group textarea::placeholder {
color: #5a5a5a;
font-style: italic;
}
/* Textarea */
.form-group textarea {
resize: vertical;
min-height: 140px;
}
/* Select: custom arrow */
.form-group select {
appearance: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1l5 5 5-5' stroke='%235a5a5a' stroke-width='1.5' fill='none' stroke-linecap='round'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 1rem center;
padding-right: 2.5rem;
}
/* Submit button alignment */
.form-submit {
display: flex;
justify-content: flex-end;
}

The CSS is organized in a clear progression:

  1. Section layoutcontact-section padding separates the form from surrounding page content
  2. Form layout.contact-form is a flex column; gap: 1.5rem spaces all groups without individual margins; max-width: 640px keeps the form comfortable to fill out on wide screens
  3. Group layout.form-group is a flex column; gap: 0.5rem tightly connects the label to its input
  4. Label — slightly smaller and bolder than body text, clearly distinct from the input content
  5. Controls — shared rules for all three interactive elements: width, padding, border, background, font, transition
  6. States — hover, focus, invalid, disabled — in that order. CSS applies them by specificity, but the cascade means later declarations override earlier ones for the same specificity, so define in the order they should naturally override
  7. Placeholders — subordinate visual treatment
  8. Textarea specifics — resize control and minimum height
  9. Select specifics — native arrow removed, custom SVG arrow added
  10. Button alignment — right-aligned with flex-end

In Module 08 (Styling STO End-to-End) you will convert all hardcoded hex values to CSS custom properties:

/* This lesson */
border-color: #2c4a1e;
box-shadow: 0 0 0 3px rgba(44, 74, 30, 0.2);
/* Module 08 equivalent */
border-color: var(--color-primary);
box-shadow: 0 0 0 3px rgba(44, 74, 30, 0.2); /* rgba stays hardcoded */

The contact form rules themselves do not change — only the color references become variables defined in :root. Everything you built here carries directly into the final module.

Test the complete contact form:

  1. Add the full CSS block above to style.css. Open contact.html.

  2. Run through the default state: all inputs should have the warm gray border, white background, and your page font.

  3. Hover over each field — the border should darken slightly. Click into it — the green focus ring should appear. Tab through every field with the keyboard — each one should show the focus ring.

  4. Test placeholder text: the placeholder should be gray and italic, clearly different from typed text.

  5. Type in the Message textarea. Drag the resize handle at the bottom-right — it should allow vertical resizing but not horizontal.

  6. Test email validation: type notanemail in the Email field, then click elsewhere. A red border and shadow should appear. Type hello@example.com — the red should disappear.

  7. Test the empty-field timing: clear the Email field completely. The red border should NOT appear for an empty required field — the :not(:placeholder-shown) rule only applies after the user has typed something invalid.

  8. Open DevTools and manually add the disabled attribute to the select element. It should fade to 50% opacity with the alternate background.

  9. Click “Send Message” — the button should lift with a shadow. Hold the click — it should press back down. Tab to the button with keyboard — the focus ring should be visible.

Across the six lessons in this module you have:

  • Reset browser defaults that make form elements look like OS-native controls
  • Applied font: inherit so all form elements use the page font
  • Styled inputs, textareas, and selects with consistent padding, border, background, and border-radius
  • Replaced outline: none with a custom box-shadow focus ring — maintaining accessibility
  • Added ::placeholder styles that are visually subordinate to entered text
  • Controlled textarea resize behavior and minimum height
  • Reset the browser button default and built the .btn/.btn-primary design
  • Applied :hover, :active, :disabled, and :focus states to buttons
  • Removed the native <select> arrow and added a custom CSS-drawn chevron
  • Used Flexbox to stack labels above inputs, space form groups evenly, and right-align the submit button
  • Styled the complete state progression: default → hover → focus → invalid → disabled
  • Used :invalid:not(:placeholder-shown) to show validation errors only after user interaction

Module 07 covers Responsive Design — making the STO site adapt from mobile to desktop using the mobile-first methodology and media queries.