Wszystkie wpisy, których autorem jest admin

How to Create Multi-Step Forms With Vanilla JavaScript and CSS

Post pobrano z: How to Create Multi-Step Forms With Vanilla JavaScript and CSS

Multi-step forms are a good choice when your form is large and has many controls. No one wants to scroll through a super-long form on a mobile device. By grouping controls on a screen-by-screen basis, we can improve the experience of filling out long, complex forms.

But when was the last time you developed a multi-step form? Does that even sound fun to you? There’s so much to think about and so many moving pieces that need to be managed that I wouldn’t blame you for resorting to a form library or even some type of form widget that handles it all for you.

But doing it by hand can be a good exercise and a great way to polish the basics. I’ll show you how I built my first multi-step form, and I hope you’ll not only see how approachable it can be but maybe even spot areas to make my work even better.

We’ll walk through the structure together. We’ll build a job application, which I think many of us can relate to these recent days. I’ll scaffold the baseline HTML, CSS, and JavaScript first, and then we’ll look at considerations for accessibility and validation.

I’ve created a GitHub repo for the final code if you want to refer to it along the way.

The structure of a multi-step form

Our job application form has four sections, the last of which is a summary view, where we show the user all their answers before they submit them. To achieve this, we divide the HTML into four sections, each identified with an ID, and add navigation at the bottom of the page. I’ll give you that baseline HTML in the next section.

Navigating the user to move through sections means we’ll also include a visual indicator for what step they are at and how many steps are left. This indicator can be a simple dynamic text that updates according to the active step or a fancier progress bar type of indicator. We’ll do the former to keep things simple and focused on the multi-step nature of the form.,

The structure and basic styles

We’ll focus more on the logic, but I will provide the code snippets and a link to the complete code at the end.

Let’s start by creating a folder to hold our pages. Then, create an index.html file and paste the following into it:

Open HTML
<form id="myForm">
  <section class="group-one" id="one">
    <div class="form-group">
      <div class="form-control">
        <label for="name">Name <span style="color: red;">*</span></label>
        <input type="text" id="name" name="name" placeholder="Enter your name">
      </div>

      <div class="form-control">
        <label for="idNum">ID number <span style="color: red;">*</span></label>
        <input type="number" id="idNum" name="idNum" placeholder="Enter your ID number">
      </div>
    </div>

    <div class="form-group">
      <div class="form-control">
        <label for="email">Email <span style="color: red;">*</span></label>
        <input type="email" id="email" name="email" placeholder="Enter your email">
      </div>

      <div class="form-control">
        <label for="birthdate">Date of Birth <span style="color: red;">*</span></label>
        <input type="date" id="birthdate" name="birthdate" max="2006-10-01" min="1924-01-01">
      </div>
    </div>
  </section>

  <section class="group-two" id="two">
    <div class="form-control">
      <label for="document">Upload CV <span style="color: red;">*</span></label>
      <input type="file" name="document" id="document">
    </div>

    <div class="form-control">
      <label for="department">Department <span style="color: red;">*</span></label>
      <select id="department" name="department">
        <option value="">Select a department</option>
        <option value="hr">Human Resources</option>
        <option value="it">Information Technology</option>
        <option value="finance">Finance</option>
      </select>
    </div>
  </section>

  <section class="group-three" id="three">
    <div class="form-control">
      <label for="skills">Skills (Optional)</label>
      <textarea id="skills" name="skills" rows="4" placeholder="Enter your skills"></textarea>
    </div>

    <div class="form-control">
      <input type="checkbox" name="terms" id="terms">
      <label for="terms">I agree to the terms and conditions <span style="color: red;">*</span></label>
    </div>

    <button id="btn" type="submit">Confirm and Submit</button>
  </section>
  
  <div class="arrows">
    <button type="button" id="navLeft">Previous</button>
    <span id="stepInfo"></span>
    <button type="button" id="navRight">Next</button>
  </div>
</form>

<script src="script.js"></script>

Looking at the code, you can see three sections and the navigation group. The sections contain form inputs and no native form validation. This is to give us better control of displaying the error messages because native form validation is only triggered when you click the submit button.

Next, create a styles.css file and paste this into it:

Open base styles
:root {
  --primary-color: #8c852a;
  --secondary-color: #858034;
}

body {
  font-family: sans-serif;
  line-height: 1.4;
  margin: 0 auto;
  padding: 20px;
  background-color: #f4f4f4;
  max-width: 600px;
}

h1 {
  text-align: center;
}

form {
  background: #fff;
  padding: 40px;
  border-radius: 5px;
  box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
  display: flex;
  flex-direction: column;
}

.form-group {
  display: flex;
  gap: 7%;

}

.form-group > div {
  width: 100%;
}

input:not([type="checkbox"]),
select,
textarea {
  width: 100%;
  padding: 8px;
  border: 1px solid #ddd;
  border-radius: 4px;
}

.form-control {
  margin-bottom: 15px;
}

button {
  display: block;
  width: 100%;
  padding: 10px;
  color: white;
  background-color: var(--primary-color);
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-size: 16px;

}

button:hover {
  background-color: var(--secondary-color);
}

.group-two, .group-three {
  display: none;
}

.arrows {
  display: flex;
  justify-content: space-between
  align-items: center;
  margin-top: 10px;
}

#navLeft, #navRight {
  width: fit-content;
}

@media screen and (max-width: 600px) {
  .form-group {
    flex-direction: column;
  }
}

Open up the HTML file in the browser, and you should get something like the two-column layout in the following screenshot, complete with the current page indicator and navigation.

Page one of a three-page form with four fields in a two-column layout

Adding functionality with vanilla JavaScript

Now, create a script.js file in the same directory as the HTML and CSS files and paste the following JavaScript into it:

Open base scripts
const stepInfo = document.getElementById("stepInfo");
const navLeft = document.getElementById("navLeft");
const navRight = document.getElementById("navRight");
const form = document.getElementById("myForm");
const formSteps = ["one", "two", "three"];

let currentStep = 0;

function updateStepVisibility() {
  formSteps.forEach((step) => {
    document.getElementById(step).style.display = "none";
  });

  document.getElementById(formSteps[currentStep]).style.display = "block";
  stepInfo.textContent = `Step ${currentStep + 1} of ${formSteps.length}`;
  navLeft.style.display = currentStep === 0 ? "none" : "block";
  navRight.style.display =
  currentStep === formSteps.length - 1 ? "none" : "block";
}

document.addEventListener("DOMContentLoaded", () => {
  navLeft.style.display = "none";
  updateStepVisibility();
  navRight.addEventListener("click", () => {
    if (currentStep < formSteps.length - 1) {
      currentStep++;
      updateStepVisibility();
    }
  });

  navLeft.addEventListener("click", () => {
    if (currentStep > 0) {
      currentStep--;
      updateStepVisibility();
    }
  });
});

This script defines a method that shows and hides the section depending on the formStep values that correspond to the IDs of the form sections. It updates stepInfo with the current active section of the form. This dynamic text acts as a progress indicator to the user.

It then adds logic that waits for the page to load and click events to the navigation buttons to enable cycling through the different form sections. If you refresh your page, you will see that the multi-step form works as expected.

Multi-step form navigation

Let’s dive deeper into what the Javascript code above is doing. In the updateStepVisibility() function, we first hide all the sections to have a clean slate:

formSteps.forEach((step) => {
  document.getElementById(step).style.display = "none";
});

Then, we show the currently active section:

document.getElementById(formSteps[currentStep]).style.display = "block";`

Next, we update the text that indicators progress through the form:

stepInfo.textContent = `Step ${currentStep + 1} of ${formSteps.length}`;

Finally, we hide the Previous button if we are at the first step and hide the Next button if we are at the last section:

navLeft.style.display = currentStep === 0 ? "none" : "block";
navRight.style.display = currentStep === formSteps.length - 1 ? "none" : "block";

Let’s look at what happens when the page loads. We first hide the Previous button as the form loads on the first section:

document.addEventListener("DOMContentLoaded", () => {
navLeft.style.display = "none";
updateStepVisibility();

Then we grab the Next button and add a click event that conditionally increments the current step count and then calls the updateStepVisibility() function, which then updates the new section to be displayed:

navRight.addEventListener("click", () => {
  if (currentStep < formSteps.length - 1) {
    currentStep++;
    updateStepVisibility();
  }
});

Finally, we grab the Previous button and do the same thing but in reverse. Here, we are conditionally decrementing the step count and calling the updateStepVisibility():

navLeft.addEventListener("click", () => {
  if (currentStep > 0) {
    currentStep--;
    updateStepVisibility();
  }
});

Handling errors

Have you ever spent a good 10+ minutes filling out a form only to submit it and get vague errors telling you to correct this and that? I prefer it when a form tells me right away that something’s amiss so that I can correct it before I ever get to the Submit button. That’s what we’ll do in our form.

Our principle is to clearly indicate which controls have errors and give meaningful error messages. Clear errors as the user takes necessary actions. Let’s add some validation to our form. First, let’s grab the necessary input elements and add this to the existing ones:

const nameInput = document.getElementById("name");
const idNumInput = document.getElementById("idNum");
const emailInput = document.getElementById("email");
const birthdateInput = document.getElementById("birthdate")
const documentInput = document.getElementById("document");
const departmentInput = document.getElementById("department");
const termsCheckbox = document.getElementById("terms");
const skillsInput = document.getElementById("skills");

Then, add a function to validate the steps:

Open validation script
function validateStep(step) {
  let isValid = true;
  
  if (step === 0) {
    if (nameInput.value.trim() === "") 
    showError(nameInput, "Name is required");
    isValid = false;
  }

  if (idNumInput.value.trim() === "") {
    showError(idNumInput, "ID number is required");
    isValid = false;
  }

  if (emailInput.value.trim() === "" || !emailInput.validity.valid) {
    showError(emailInput, "A valid email is required");
    isValid = false;
  }

  if (birthdateInput.value === "") {
    showError(birthdateInput, "Date of birth is required");
    isValid = false;
  }
  
  else if (step === 1) {

    if (!documentInput.files[0]) {
      showError(documentInput, "CV is required");
      isValid = false;
    }

    if (departmentInput.value === "") {
      showError(departmentInput, "Department selection is required");
      isValid = false;
    }
  } else if (step === 2) {

    if (!termsCheckbox.checked) {
      showError(termsCheckbox, "You must accept the terms and conditions");
      isValid = false;
    }
  }

  return isValid;
}

Here, we check if each required input has some value and if the email input has a valid input. Then, we set the isValid boolean accordingly. We also call a showError() function, which we haven’t defined yet.

Paste this code above the validateStep() function:

function showError(input, message) {
  const formControl = input.parentElement;
  const errorSpan = formControl.querySelector(".error-message");
  input.classList.add("error");
  errorSpan.textContent = message;
}

Now, add the following styles to the stylesheet:

Open validation styles
input:focus, select:focus, textarea:focus {
  outline: .5px solid var(--primary-color);
}

input.error, select.error, textarea.error {
  outline: .5px solid red;
}

.error-message {
  font-size: x-small;
  color: red;
  display: block;
  margin-top: 2px;
}

.arrows {
  color: var(--primary-color);
  font-size: 18px;
  font-weight: 900;

}

#navLeft, #navRight {
  display: flex;
  align-items: center;
  gap: 10px;
}

#stepInfo {
  color: var(--primary-color);
}

If you refresh the form, you will see that the buttons do not take you to the next section till the inputs are considered valid:

A personnel registration form with fields for name, ID number, email, and date of birth, all marked as required. Step 1 of 3 with a "Next" button.

Finally, we want to add real-time error handling so that the errors go away when the user starts inputting the correct information. Add this function below the validateStep() function:

Open real-time validation script
function setupRealtimeValidation() {
  nameInput.addEventListener("input", () => {
    if (nameInput.value.trim() !== "") clearError(nameInput);
  });

  idNumInput.addEventListener("input", () => {
    if (idNumInput.value.trim() !== "") clearError(idNumInput);
  });
  
  emailInput.addEventListener("input", () => {
    if (emailInput.validity.valid) clearError(emailInput);
  });
  
  birthdateInput.addEventListener("change", () => {
    if (birthdateInput.value !== "") clearError(birthdateInput);
  });
  
  documentInput.addEventListener("change", () => {
    if (documentInput.files[0]) clearError(documentInput);
  });
  
  departmentInput.addEventListener("change", () => {
    if (departmentInput.value !== "") clearError(departmentInput);
  });
  
  termsCheckbox.addEventListener("change", () => {
    if (termsCheckbox.checked) clearError(termsCheckbox);
  });
}

This function clears the errors if the input is no longer invalid by listening to input and change events then calling a function to clear the errors. Paste the clearError() function below the showError() one:

function clearError(input) {
  const formControl = input.parentElement;
  const errorSpan = formControl.querySelector(".error-message");
  input.classList.remove("error");
  errorSpan.textContent = "";
}

And now the errors clear when the user types in the correct value:

A personnel registration form with fields for name, email, ID number, and date of birth. Error messages indicate missing or invalid input. A "Step 1 of 3" label and "Next" button are shown.

The multi-step form now handles errors gracefully. If you do decide to keep the errors till the end of the form, then at the very least, jump the user back to the erroring form control and show some indication of how many errors they need to fix.

Handling form submission

In a multi-step form, it is valuable to show the user a summary of all their answers at the end before they submit and to offer them an option to edit their answers if necessary. The person can’t see the previous steps without navigating backward, so showing a summary at the last step gives assurance and a chance to correct any mistakes.

Let’s add a fourth section to the markup to hold this summary view and move the submit button within it. Paste this just below the third section in index.html:

Open HTML
<section class="group-four" id="four">
  <div class="summary-section">
    <p>Name: </p>
    <p id="name-val"></p>
    <button type="button" class="edit-btn" id="name-edit">
    <span>✎</span>
    <span>Edit</span>
    </button>
  </div>
  
  <div class="summary-section">
    <p>ID Number: </p>
    <p id="id-val"></p>
    <button type="button" class="edit-btn" id="id-edit">
    <span>✎</span>
    <span>Edit</span>
    </button>
  </div>
  
  <div class="summary-section">
    <p>Email: </p>
    <p id="email-val"></p>
    <button type="button" class="edit-btn" id="email-edit">
    <span>✎</span>
    <span>Edit</span>
    </button>
  </div>
  
  <div class="summary-section">
    <p>Date of Birth: </p>
    <p id="bd-val"></p>
    <button type="button" class="edit-btn" id="bd-edit">
    <span>✎</span>
    <span>Edit</span>
    </button>
  </div>
  
  <div class="summary-section">
    <p>CV/Resume: </p>
    <p id="cv-val"></p>
    <button type="button" class="edit-btn" id="cv-edit">
      <span>✎</span>
      <span>Edit</span>
    </button>
  </div>
    
  <div class="summary-section">
    <p>Department: </p>
    <p id="dept-val"></p>
    <button type="button" class="edit-btn" id="dept-edit">
      <span>✎</span>
      <span>Edit</span>
    </button>
  </div>
    
  <div class="summary-section">
    <p>Skills: </p>
    <p id="skills-val"></p>
    <button type="button" class="edit-btn" id="skills-edit">
      <span>✎</span>
      <span>Edit</span>
    </button>
  </div>
  
  <button id="btn" type="submit">Confirm and Submit</button>
</section>

Then update the formStep in your Javascript to read:

const formSteps = ["one", "two", "three", "four"];

Finally, add the following classes to styles.css:

.summary-section {
  display: flex;
  align-items: center;
  gap: 10px;
}

.summary-section p:first-child {
  width: 30%;
  flex-shrink: 0;
  border-right: 1px solid var(--secondary-color);
}

.summary-section p:nth-child(2) {
  width: 45%;
  flex-shrink: 0;
  padding-left: 10px;
}

.edit-btn {
  width: 25%;
  margin-left: auto;
  background-color: transparent;
  color: var(--primary-color);
  border: .7px solid var(--primary-color);
  border-radius: 5px;
  padding: 5px;
}

.edit-btn:hover {
  border: 2px solid var(--primary-color);
  font-weight: bolder;
  background-color: transparent;
}

Now, add the following to the top of the script.js file where the other consts are:

const nameVal = document.getElementById("name-val");
const idVal = document.getElementById("id-val");
const emailVal = document.getElementById("email-val");
const bdVal = document.getElementById("bd-val")
const cvVal = document.getElementById("cv-val");
const deptVal = document.getElementById("dept-val");
const skillsVal = document.getElementById("skills-val");
const editButtons = 
  "name-edit": 0,
  "id-edit": 0,
  "email-edit": 0,
  "bd-edit": 0,
  "cv-edit": 1,
  "dept-edit": 1,
  "skills-edit": 2
};

Then add this function in scripts.js:

function updateSummaryValues() {
  nameVal.textContent = nameInput.value;
  idVal.textContent = idNumInput.value;
  emailVal.textContent = emailInput.value;
  bdVal.textContent = birthdateInput.value;

  const fileName = documentInput.files[0]?.name;
  if (fileName) 
  const extension = fileName.split(".").pop();
  const baseName = fileName.split(".")[0];
  const truncatedName = baseName.length > 10 ? baseName.substring(0, 10) + "..." : baseName;
  cvVal.textContent = `${truncatedName}.${extension}`;
  } else {
    cvVal.textContent = "No file selected";
  }

  deptVal.textContent = departmentInput.value;
  skillsVal.textContent = skillsInput.value || "No skills submitted";
}

This dynamically inserts the input values into the summary section of the form, truncates the file names, and offers a fallback text for the input that was not required.

Then update the updateStepVisibility() function to call the new function:

function updateStepVisibility() {
  formSteps.forEach((step) => {
    document.getElementById(step).style.display = "none";
  });

  document.getElementById(formSteps[currentStep]).style.display = "block";
  stepInfo.textContent = `Step ${currentStep + 1} of ${formSteps.length}`;
  if (currentStep === 3) {
    updateSummaryValues();
  }

  navLeft.style.display = currentStep === 0 ? "none" : "block";
  navRight.style.display = currentStep === formSteps.length - 1 ? "none" : "block";
}

Finally, add this to the DOMContentLoaded event listener:

Object.keys(editButtons).forEach((buttonId) => {
  const button = document.getElementById(buttonId);
  button.addEventListener("click", (e) => {
    currentStep = editButtons[buttonId];
    updateStepVisibility();
  });
});

Running the form, you should see that the summary section shows all the inputted values and allows the user to edit any before submitting the information:

Personnel registration form displaying personal details with options to edit each field, a "Confirm and Submit" button, and navigation for previous steps.

And now, we can submit our form:

form.addEventListener("submit", (e) => {
  e.preventDefault();

  if (validateStep(2)) {
    alert("Form submitted successfully!");
    form.reset();
    currentFormStep = 0;
    updateStepVisibility();
}
});

Our multi-step form now allows the user to edit and see all the information they provide before submitting it.

Accessibility tips

Making multi-step forms accessible starts with the basics: using semantic HTML. This is half the battle. It is closely followed by using appropriate form labels.

Other ways to make forms more accessible include giving enough room to elements that must be clicked on small screens and giving meaningful descriptions to the form navigation and progress indicators.

Offering feedback to the user is an important part of it; it’s not great to auto-dismiss user feedback after a certain amount of time but to allow the user to dismiss it themselves. Paying attention to contrast and font choice is important, too, as they both affect how readable your form is.

Let’s make the following adjustments to the markup for more technical accessibility:

  1. Add aria-required="true" to all inputs except the skills one. This lets screen readers know the fields are required without relying on native validation.
  2. Add role="alert" to the error spans. This helps screen readers know to give it importance when the input is in an error state.
  3. Add role="status" aria-live="polite" to the .stepInfo. This will help screen readers understand that the step info keeps tabs on a state, and the aria-live being set to polite indicates that should the value change, it does not need to immediately announce it.

In the script file, replace the showError() and clearError() functions with the following:

function showError(input, message) {
  const formControl = input.parentElement;
  const errorSpan = formControl.querySelector(".error-message");
  input.classList.add("error");
  input.setAttribute("aria-invalid", "true");
  input.setAttribute("aria-describedby", errorSpan.id);
  errorSpan.textContent = message;
  }

  function clearError(input) {
  const formControl = input.parentElement;
  const errorSpan = formControl.querySelector(".error-message");
  input.classList.remove("error");
  input.removeAttribute("aria-invalid");
  input.removeAttribute("aria-describedby");
  errorSpan.textContent = "";
}

Here, we programmatically add and remove attributes that explicitly tie the input with its error span and show that it is in an invalid state.

Finally, let’s add focus on the first input of every section; add the following code to the end of the updateStepVisibility() function:

const currentStepElement = document.getElementById(formSteps[currentStep]);
const firstInput = currentStepElement.querySelector(
  "input, select, textarea"
);

if (firstInput) {
  firstInput.focus();
}

And with that, the multi-step form is much more accessible.

Conclusion

There we go, a four-part multi-step form for a job application! As I said at the top of this article, there’s a lot to juggle — so much so that I wouldn’t fault you for looking for an out-of-the-box solution.

But if you have to hand-roll a multi-step form, hopefully now you see it’s not a death sentence. There’s a happy path that gets you there, complete with navigation and validation, without turning away from good, accessible practices.

And this is just how I approached it! Again, I took this on as a personal challenge to see how far I could get, and I’m pretty happy with it. But I’d love to know if you see additional opportunities to make this even more mindful of the user experience and considerate of accessibility.

References

Here are some relevant links I referred to when writing this article:

  1. How to Structure a Web Form (MDN)
  2. Multi-page Forms (W3C.org)
  3. Create accessible forms (A11y Project)

How to Create Multi-Step Forms With Vanilla JavaScript and CSS originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

How to Create Multi-Step Forms With Vanilla JavaScript and CSS

Post pobrano z: How to Create Multi-Step Forms With Vanilla JavaScript and CSS

Multi-step forms are a good choice when your form is large and has many controls. No one wants to scroll through a super-long form on a mobile device. By grouping controls on a screen-by-screen basis, we can improve the experience of filling out long, complex forms.

But when was the last time you developed a multi-step form? Does that even sound fun to you? There’s so much to think about and so many moving pieces that need to be managed that I wouldn’t blame you for resorting to a form library or even some type of form widget that handles it all for you.

But doing it by hand can be a good exercise and a great way to polish the basics. I’ll show you how I built my first multi-step form, and I hope you’ll not only see how approachable it can be but maybe even spot areas to make my work even better.

We’ll walk through the structure together. We’ll build a job application, which I think many of us can relate to these recent days. I’ll scaffold the baseline HTML, CSS, and JavaScript first, and then we’ll look at considerations for accessibility and validation.

I’ve created a GitHub repo for the final code if you want to refer to it along the way.

The structure of a multi-step form

Our job application form has four sections, the last of which is a summary view, where we show the user all their answers before they submit them. To achieve this, we divide the HTML into four sections, each identified with an ID, and add navigation at the bottom of the page. I’ll give you that baseline HTML in the next section.

Navigating the user to move through sections means we’ll also include a visual indicator for what step they are at and how many steps are left. This indicator can be a simple dynamic text that updates according to the active step or a fancier progress bar type of indicator. We’ll do the former to keep things simple and focused on the multi-step nature of the form.,

The structure and basic styles

We’ll focus more on the logic, but I will provide the code snippets and a link to the complete code at the end.

Let’s start by creating a folder to hold our pages. Then, create an index.html file and paste the following into it:

Open HTML
<form id="myForm">
  <section class="group-one" id="one">
    <div class="form-group">
      <div class="form-control">
        <label for="name">Name <span style="color: red;">*</span></label>
        <input type="text" id="name" name="name" placeholder="Enter your name">
      </div>

      <div class="form-control">
        <label for="idNum">ID number <span style="color: red;">*</span></label>
        <input type="number" id="idNum" name="idNum" placeholder="Enter your ID number">
      </div>
    </div>

    <div class="form-group">
      <div class="form-control">
        <label for="email">Email <span style="color: red;">*</span></label>
        <input type="email" id="email" name="email" placeholder="Enter your email">
      </div>

      <div class="form-control">
        <label for="birthdate">Date of Birth <span style="color: red;">*</span></label>
        <input type="date" id="birthdate" name="birthdate" max="2006-10-01" min="1924-01-01">
      </div>
    </div>
  </section>

  <section class="group-two" id="two">
    <div class="form-control">
      <label for="document">Upload CV <span style="color: red;">*</span></label>
      <input type="file" name="document" id="document">
    </div>

    <div class="form-control">
      <label for="department">Department <span style="color: red;">*</span></label>
      <select id="department" name="department">
        <option value="">Select a department</option>
        <option value="hr">Human Resources</option>
        <option value="it">Information Technology</option>
        <option value="finance">Finance</option>
      </select>
    </div>
  </section>

  <section class="group-three" id="three">
    <div class="form-control">
      <label for="skills">Skills (Optional)</label>
      <textarea id="skills" name="skills" rows="4" placeholder="Enter your skills"></textarea>
    </div>

    <div class="form-control">
      <input type="checkbox" name="terms" id="terms">
      <label for="terms">I agree to the terms and conditions <span style="color: red;">*</span></label>
    </div>

    <button id="btn" type="submit">Confirm and Submit</button>
  </section>
  
  <div class="arrows">
    <button type="button" id="navLeft">Previous</button>
    <span id="stepInfo"></span>
    <button type="button" id="navRight">Next</button>
  </div>
</form>

<script src="script.js"></script>

Looking at the code, you can see three sections and the navigation group. The sections contain form inputs and no native form validation. This is to give us better control of displaying the error messages because native form validation is only triggered when you click the submit button.

Next, create a styles.css file and paste this into it:

Open base styles
:root {
  --primary-color: #8c852a;
  --secondary-color: #858034;
}

body {
  font-family: sans-serif;
  line-height: 1.4;
  margin: 0 auto;
  padding: 20px;
  background-color: #f4f4f4;
  max-width: 600px;
}

h1 {
  text-align: center;
}

form {
  background: #fff;
  padding: 40px;
  border-radius: 5px;
  box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
  display: flex;
  flex-direction: column;
}

.form-group {
  display: flex;
  gap: 7%;

}

.form-group > div {
  width: 100%;
}

input:not([type="checkbox"]),
select,
textarea {
  width: 100%;
  padding: 8px;
  border: 1px solid #ddd;
  border-radius: 4px;
}

.form-control {
  margin-bottom: 15px;
}

button {
  display: block;
  width: 100%;
  padding: 10px;
  color: white;
  background-color: var(--primary-color);
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-size: 16px;

}

button:hover {
  background-color: var(--secondary-color);
}

.group-two, .group-three {
  display: none;
}

.arrows {
  display: flex;
  justify-content: space-between
  align-items: center;
  margin-top: 10px;
}

#navLeft, #navRight {
  width: fit-content;
}

@media screen and (max-width: 600px) {
  .form-group {
    flex-direction: column;
  }
}

Open up the HTML file in the browser, and you should get something like the two-column layout in the following screenshot, complete with the current page indicator and navigation.

Page one of a three-page form with four fields in a two-column layout

Adding functionality with vanilla JavaScript

Now, create a script.js file in the same directory as the HTML and CSS files and paste the following JavaScript into it:

Open base scripts
const stepInfo = document.getElementById("stepInfo");
const navLeft = document.getElementById("navLeft");
const navRight = document.getElementById("navRight");
const form = document.getElementById("myForm");
const formSteps = ["one", "two", "three"];

let currentStep = 0;

function updateStepVisibility() {
  formSteps.forEach((step) => {
    document.getElementById(step).style.display = "none";
  });

  document.getElementById(formSteps[currentStep]).style.display = "block";
  stepInfo.textContent = `Step ${currentStep + 1} of ${formSteps.length}`;
  navLeft.style.display = currentStep === 0 ? "none" : "block";
  navRight.style.display =
  currentStep === formSteps.length - 1 ? "none" : "block";
}

document.addEventListener("DOMContentLoaded", () => {
  navLeft.style.display = "none";
  updateStepVisibility();
  navRight.addEventListener("click", () => {
    if (currentStep < formSteps.length - 1) {
      currentStep++;
      updateStepVisibility();
    }
  });

  navLeft.addEventListener("click", () => {
    if (currentStep > 0) {
      currentStep--;
      updateStepVisibility();
    }
  });
});

This script defines a method that shows and hides the section depending on the formStep values that correspond to the IDs of the form sections. It updates stepInfo with the current active section of the form. This dynamic text acts as a progress indicator to the user.

It then adds logic that waits for the page to load and click events to the navigation buttons to enable cycling through the different form sections. If you refresh your page, you will see that the multi-step form works as expected.

Multi-step form navigation

Let’s dive deeper into what the Javascript code above is doing. In the updateStepVisibility() function, we first hide all the sections to have a clean slate:

formSteps.forEach((step) => {
  document.getElementById(step).style.display = "none";
});

Then, we show the currently active section:

document.getElementById(formSteps[currentStep]).style.display = "block";`

Next, we update the text that indicators progress through the form:

stepInfo.textContent = `Step ${currentStep + 1} of ${formSteps.length}`;

Finally, we hide the Previous button if we are at the first step and hide the Next button if we are at the last section:

navLeft.style.display = currentStep === 0 ? "none" : "block";
navRight.style.display = currentStep === formSteps.length - 1 ? "none" : "block";

Let’s look at what happens when the page loads. We first hide the Previous button as the form loads on the first section:

document.addEventListener("DOMContentLoaded", () => {
navLeft.style.display = "none";
updateStepVisibility();

Then we grab the Next button and add a click event that conditionally increments the current step count and then calls the updateStepVisibility() function, which then updates the new section to be displayed:

navRight.addEventListener("click", () => {
  if (currentStep < formSteps.length - 1) {
    currentStep++;
    updateStepVisibility();
  }
});

Finally, we grab the Previous button and do the same thing but in reverse. Here, we are conditionally decrementing the step count and calling the updateStepVisibility():

navLeft.addEventListener("click", () => {
  if (currentStep > 0) {
    currentStep--;
    updateStepVisibility();
  }
});

Handling errors

Have you ever spent a good 10+ minutes filling out a form only to submit it and get vague errors telling you to correct this and that? I prefer it when a form tells me right away that something’s amiss so that I can correct it before I ever get to the Submit button. That’s what we’ll do in our form.

Our principle is to clearly indicate which controls have errors and give meaningful error messages. Clear errors as the user takes necessary actions. Let’s add some validation to our form. First, let’s grab the necessary input elements and add this to the existing ones:

const nameInput = document.getElementById("name");
const idNumInput = document.getElementById("idNum");
const emailInput = document.getElementById("email");
const birthdateInput = document.getElementById("birthdate")
const documentInput = document.getElementById("document");
const departmentInput = document.getElementById("department");
const termsCheckbox = document.getElementById("terms");
const skillsInput = document.getElementById("skills");

Then, add a function to validate the steps:

Open validation script
function validateStep(step) {
  let isValid = true;
  
  if (step === 0) {
    if (nameInput.value.trim() === "") 
    showError(nameInput, "Name is required");
    isValid = false;
  }

  if (idNumInput.value.trim() === "") {
    showError(idNumInput, "ID number is required");
    isValid = false;
  }

  if (emailInput.value.trim() === "" || !emailInput.validity.valid) {
    showError(emailInput, "A valid email is required");
    isValid = false;
  }

  if (birthdateInput.value === "") {
    showError(birthdateInput, "Date of birth is required");
    isValid = false;
  }
  
  else if (step === 1) {

    if (!documentInput.files[0]) {
      showError(documentInput, "CV is required");
      isValid = false;
    }

    if (departmentInput.value === "") {
      showError(departmentInput, "Department selection is required");
      isValid = false;
    }
  } else if (step === 2) {

    if (!termsCheckbox.checked) {
      showError(termsCheckbox, "You must accept the terms and conditions");
      isValid = false;
    }
  }

  return isValid;
}

Here, we check if each required input has some value and if the email input has a valid input. Then, we set the isValid boolean accordingly. We also call a showError() function, which we haven’t defined yet.

Paste this code above the validateStep() function:

function showError(input, message) {
  const formControl = input.parentElement;
  const errorSpan = formControl.querySelector(".error-message");
  input.classList.add("error");
  errorSpan.textContent = message;
}

Now, add the following styles to the stylesheet:

Open validation styles
input:focus, select:focus, textarea:focus {
  outline: .5px solid var(--primary-color);
}

input.error, select.error, textarea.error {
  outline: .5px solid red;
}

.error-message {
  font-size: x-small;
  color: red;
  display: block;
  margin-top: 2px;
}

.arrows {
  color: var(--primary-color);
  font-size: 18px;
  font-weight: 900;

}

#navLeft, #navRight {
  display: flex;
  align-items: center;
  gap: 10px;
}

#stepInfo {
  color: var(--primary-color);
}

If you refresh the form, you will see that the buttons do not take you to the next section till the inputs are considered valid:

A personnel registration form with fields for name, ID number, email, and date of birth, all marked as required. Step 1 of 3 with a "Next" button.

Finally, we want to add real-time error handling so that the errors go away when the user starts inputting the correct information. Add this function below the validateStep() function:

Open real-time validation script
function setupRealtimeValidation() {
  nameInput.addEventListener("input", () => {
    if (nameInput.value.trim() !== "") clearError(nameInput);
  });

  idNumInput.addEventListener("input", () => {
    if (idNumInput.value.trim() !== "") clearError(idNumInput);
  });
  
  emailInput.addEventListener("input", () => {
    if (emailInput.validity.valid) clearError(emailInput);
  });
  
  birthdateInput.addEventListener("change", () => {
    if (birthdateInput.value !== "") clearError(birthdateInput);
  });
  
  documentInput.addEventListener("change", () => {
    if (documentInput.files[0]) clearError(documentInput);
  });
  
  departmentInput.addEventListener("change", () => {
    if (departmentInput.value !== "") clearError(departmentInput);
  });
  
  termsCheckbox.addEventListener("change", () => {
    if (termsCheckbox.checked) clearError(termsCheckbox);
  });
}

This function clears the errors if the input is no longer invalid by listening to input and change events then calling a function to clear the errors. Paste the clearError() function below the showError() one:

function clearError(input) {
  const formControl = input.parentElement;
  const errorSpan = formControl.querySelector(".error-message");
  input.classList.remove("error");
  errorSpan.textContent = "";
}

And now the errors clear when the user types in the correct value:

A personnel registration form with fields for name, email, ID number, and date of birth. Error messages indicate missing or invalid input. A "Step 1 of 3" label and "Next" button are shown.

The multi-step form now handles errors gracefully. If you do decide to keep the errors till the end of the form, then at the very least, jump the user back to the erroring form control and show some indication of how many errors they need to fix.

Handling form submission

In a multi-step form, it is valuable to show the user a summary of all their answers at the end before they submit and to offer them an option to edit their answers if necessary. The person can’t see the previous steps without navigating backward, so showing a summary at the last step gives assurance and a chance to correct any mistakes.

Let’s add a fourth section to the markup to hold this summary view and move the submit button within it. Paste this just below the third section in index.html:

Open HTML
<section class="group-four" id="four">
  <div class="summary-section">
    <p>Name: </p>
    <p id="name-val"></p>
    <button type="button" class="edit-btn" id="name-edit">
    <span>✎</span>
    <span>Edit</span>
    </button>
  </div>
  
  <div class="summary-section">
    <p>ID Number: </p>
    <p id="id-val"></p>
    <button type="button" class="edit-btn" id="id-edit">
    <span>✎</span>
    <span>Edit</span>
    </button>
  </div>
  
  <div class="summary-section">
    <p>Email: </p>
    <p id="email-val"></p>
    <button type="button" class="edit-btn" id="email-edit">
    <span>✎</span>
    <span>Edit</span>
    </button>
  </div>
  
  <div class="summary-section">
    <p>Date of Birth: </p>
    <p id="bd-val"></p>
    <button type="button" class="edit-btn" id="bd-edit">
    <span>✎</span>
    <span>Edit</span>
    </button>
  </div>
  
  <div class="summary-section">
    <p>CV/Resume: </p>
    <p id="cv-val"></p>
    <button type="button" class="edit-btn" id="cv-edit">
      <span>✎</span>
      <span>Edit</span>
    </button>
  </div>
    
  <div class="summary-section">
    <p>Department: </p>
    <p id="dept-val"></p>
    <button type="button" class="edit-btn" id="dept-edit">
      <span>✎</span>
      <span>Edit</span>
    </button>
  </div>
    
  <div class="summary-section">
    <p>Skills: </p>
    <p id="skills-val"></p>
    <button type="button" class="edit-btn" id="skills-edit">
      <span>✎</span>
      <span>Edit</span>
    </button>
  </div>
  
  <button id="btn" type="submit">Confirm and Submit</button>
</section>

Then update the formStep in your Javascript to read:

const formSteps = ["one", "two", "three", "four"];

Finally, add the following classes to styles.css:

.summary-section {
  display: flex;
  align-items: center;
  gap: 10px;
}

.summary-section p:first-child {
  width: 30%;
  flex-shrink: 0;
  border-right: 1px solid var(--secondary-color);
}

.summary-section p:nth-child(2) {
  width: 45%;
  flex-shrink: 0;
  padding-left: 10px;
}

.edit-btn {
  width: 25%;
  margin-left: auto;
  background-color: transparent;
  color: var(--primary-color);
  border: .7px solid var(--primary-color);
  border-radius: 5px;
  padding: 5px;
}

.edit-btn:hover {
  border: 2px solid var(--primary-color);
  font-weight: bolder;
  background-color: transparent;
}

Now, add the following to the top of the script.js file where the other consts are:

const nameVal = document.getElementById("name-val");
const idVal = document.getElementById("id-val");
const emailVal = document.getElementById("email-val");
const bdVal = document.getElementById("bd-val")
const cvVal = document.getElementById("cv-val");
const deptVal = document.getElementById("dept-val");
const skillsVal = document.getElementById("skills-val");
const editButtons = 
  "name-edit": 0,
  "id-edit": 0,
  "email-edit": 0,
  "bd-edit": 0,
  "cv-edit": 1,
  "dept-edit": 1,
  "skills-edit": 2
};

Then add this function in scripts.js:

function updateSummaryValues() {
  nameVal.textContent = nameInput.value;
  idVal.textContent = idNumInput.value;
  emailVal.textContent = emailInput.value;
  bdVal.textContent = birthdateInput.value;

  const fileName = documentInput.files[0]?.name;
  if (fileName) 
  const extension = fileName.split(".").pop();
  const baseName = fileName.split(".")[0];
  const truncatedName = baseName.length > 10 ? baseName.substring(0, 10) + "..." : baseName;
  cvVal.textContent = `${truncatedName}.${extension}`;
  } else {
    cvVal.textContent = "No file selected";
  }

  deptVal.textContent = departmentInput.value;
  skillsVal.textContent = skillsInput.value || "No skills submitted";
}

This dynamically inserts the input values into the summary section of the form, truncates the file names, and offers a fallback text for the input that was not required.

Then update the updateStepVisibility() function to call the new function:

function updateStepVisibility() {
  formSteps.forEach((step) => {
    document.getElementById(step).style.display = "none";
  });

  document.getElementById(formSteps[currentStep]).style.display = "block";
  stepInfo.textContent = `Step ${currentStep + 1} of ${formSteps.length}`;
  if (currentStep === 3) {
    updateSummaryValues();
  }

  navLeft.style.display = currentStep === 0 ? "none" : "block";
  navRight.style.display = currentStep === formSteps.length - 1 ? "none" : "block";
}

Finally, add this to the DOMContentLoaded event listener:

Object.keys(editButtons).forEach((buttonId) => {
  const button = document.getElementById(buttonId);
  button.addEventListener("click", (e) => {
    currentStep = editButtons[buttonId];
    updateStepVisibility();
  });
});

Running the form, you should see that the summary section shows all the inputted values and allows the user to edit any before submitting the information:

Personnel registration form displaying personal details with options to edit each field, a "Confirm and Submit" button, and navigation for previous steps.

And now, we can submit our form:

form.addEventListener("submit", (e) => {
  e.preventDefault();

  if (validateStep(2)) {
    alert("Form submitted successfully!");
    form.reset();
    currentFormStep = 0;
    updateStepVisibility();
}
});

Our multi-step form now allows the user to edit and see all the information they provide before submitting it.

Accessibility tips

Making multi-step forms accessible starts with the basics: using semantic HTML. This is half the battle. It is closely followed by using appropriate form labels.

Other ways to make forms more accessible include giving enough room to elements that must be clicked on small screens and giving meaningful descriptions to the form navigation and progress indicators.

Offering feedback to the user is an important part of it; it’s not great to auto-dismiss user feedback after a certain amount of time but to allow the user to dismiss it themselves. Paying attention to contrast and font choice is important, too, as they both affect how readable your form is.

Let’s make the following adjustments to the markup for more technical accessibility:

  1. Add aria-required="true" to all inputs except the skills one. This lets screen readers know the fields are required without relying on native validation.
  2. Add role="alert" to the error spans. This helps screen readers know to give it importance when the input is in an error state.
  3. Add role="status" aria-live="polite" to the .stepInfo. This will help screen readers understand that the step info keeps tabs on a state, and the aria-live being set to polite indicates that should the value change, it does not need to immediately announce it.

In the script file, replace the showError() and clearError() functions with the following:

function showError(input, message) {
  const formControl = input.parentElement;
  const errorSpan = formControl.querySelector(".error-message");
  input.classList.add("error");
  input.setAttribute("aria-invalid", "true");
  input.setAttribute("aria-describedby", errorSpan.id);
  errorSpan.textContent = message;
  }

  function clearError(input) {
  const formControl = input.parentElement;
  const errorSpan = formControl.querySelector(".error-message");
  input.classList.remove("error");
  input.removeAttribute("aria-invalid");
  input.removeAttribute("aria-describedby");
  errorSpan.textContent = "";
}

Here, we programmatically add and remove attributes that explicitly tie the input with its error span and show that it is in an invalid state.

Finally, let’s add focus on the first input of every section; add the following code to the end of the updateStepVisibility() function:

const currentStepElement = document.getElementById(formSteps[currentStep]);
const firstInput = currentStepElement.querySelector(
  "input, select, textarea"
);

if (firstInput) {
  firstInput.focus();
}

And with that, the multi-step form is much more accessible.

Conclusion

There we go, a four-part multi-step form for a job application! As I said at the top of this article, there’s a lot to juggle — so much so that I wouldn’t fault you for looking for an out-of-the-box solution.

But if you have to hand-roll a multi-step form, hopefully now you see it’s not a death sentence. There’s a happy path that gets you there, complete with navigation and validation, without turning away from good, accessible practices.

And this is just how I approached it! Again, I took this on as a personal challenge to see how far I could get, and I’m pretty happy with it. But I’d love to know if you see additional opportunities to make this even more mindful of the user experience and considerate of accessibility.

References

Here are some relevant links I referred to when writing this article:

  1. How to Structure a Web Form (MDN)
  2. Multi-page Forms (W3C.org)
  3. Create accessible forms (A11y Project)

How to Create Multi-Step Forms With Vanilla JavaScript and CSS originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

What ELSE is on your CSS wishlist?

Post pobrano z: What ELSE is on your CSS wishlist?

What else do we want or need CSS to do? It’s like being out late at night someplace you shouldn’t be and a stranger in a trenchcoat walks up and whispers in your ear.

“Psst. You wanna buy some async @imports? I’ve got the specificity you want.”

You know you shouldn’t entertain the idea but you do it anyway. All your friends doing Cascade Layers. What are you, a square?

I keep thinking of how amazing it is to write CSS today. There was an email exchange just this morning where I was discussing a bunch of ideas for a persistent set of controls in the UI that would have sounded bonkers even one year ago if it wasn’t for new features, like anchor positioning, scroll timelines, auto-height transitions, and popovers. We’re still in the early days of all these things — among many, many more — and have yet to see all the awesome possibilities come to fruition. Exciting times!

Chris kept a CSS wishlist, going back as far as 2013 and following up on it in 2019. We all have things we’d like to see CSS do and we always will no matter how many sparkly new features we get. Let’s revisit the ones from 2013:

  1. “I’d like to be able to select an element based on if it contains another particular selector.” Hello, :has()!
  2. ❌ “I’d like to be able to select an element based on the content it contains.”
  3. ❌ “I’d like multiple pseudo-elements.”
  4. ✅ “I’d like to be able to animate/transition something to height: auto;” Yep, we got that!
  5. 🟠 “I’d like things from Sass, like @extend@mixin, and nesting.” We got the nesting part down with some progress on mixins.
  6. ❌ “I’d like ::nth-letter::nth-word, etc.”
  7. ✅ “I’d like all the major browsers to auto-update.” This one was already fulfilled.

So, about a score of 3.5 out of 7. It could very well be that some of these things fell out of favor at some point (haven’t heard any crying for a new pseudo-element since the first wishlist). Chris re-articulated the list this way:

  • Parent queries. As in, selecting an element any-which-way, then selecting the parent of that element. We have some proof it’s possible with :focus-within.
  • Container queries. Select a particular element when the element itself is under certain conditions.
  • Standardized styling of form elements.
  • Has/Contains Selectors.
  • Transitions to auto dimensions.
  • Fixed up handling of viewport units.

And we’ve got the vast majority of those under wraps! We have ways to query parents and containers. We’re exploring stylable selects and field-sizing. We know about :has() and we’re still going gaga over transitions to intrinsic sizes. We’ve openly opined whether there’s too much CSS (there isn’t).

But what else is on your CSS wishlist? Ironically enough, Adam Argyle went through this exercise just this morning and I love the way he’s broken things down into a user-facing wishlist and a developer-facing wishlist. I mean, geez, a CSS carousel? Yes, please! I love his list and all lists like it.

We’ll round things up and put a list together — so let us know!


What ELSE is on your CSS wishlist? originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

What ELSE is on your CSS wishlist?

Post pobrano z: What ELSE is on your CSS wishlist?

What else do we want or need CSS to do? It’s like being out late at night someplace you shouldn’t be and a stranger in a trenchcoat walks up and whispers in your ear.

“Psst. You wanna buy some async @imports? I’ve got the specificity you want.”

You know you shouldn’t entertain the idea but you do it anyway. All your friends doing Cascade Layers. What are you, a square?

I keep thinking of how amazing it is to write CSS today. There was an email exchange just this morning where I was discussing a bunch of ideas for a persistent set of controls in the UI that would have sounded bonkers even one year ago if it wasn’t for new features, like anchor positioning, scroll timelines, auto-height transitions, and popovers. We’re still in the early days of all these things — among many, many more — and have yet to see all the awesome possibilities come to fruition. Exciting times!

Chris kept a CSS wishlist, going back as far as 2013 and following up on it in 2019. We all have things we’d like to see CSS do and we always will no matter how many sparkly new features we get. Let’s revisit the ones from 2013:

  1. “I’d like to be able to select an element based on if it contains another particular selector.” Hello, :has()!
  2. ❌ “I’d like to be able to select an element based on the content it contains.”
  3. ❌ “I’d like multiple pseudo-elements.”
  4. ✅ “I’d like to be able to animate/transition something to height: auto;” Yep, we got that!
  5. 🟠 “I’d like things from Sass, like @extend@mixin, and nesting.” We got the nesting part down with some progress on mixins.
  6. ❌ “I’d like ::nth-letter::nth-word, etc.”
  7. ✅ “I’d like all the major browsers to auto-update.” This one was already fulfilled.

So, about a score of 3.5 out of 7. It could very well be that some of these things fell out of favor at some point (haven’t heard any crying for a new pseudo-element since the first wishlist). Chris re-articulated the list this way:

  • Parent queries. As in, selecting an element any-which-way, then selecting the parent of that element. We have some proof it’s possible with :focus-within.
  • Container queries. Select a particular element when the element itself is under certain conditions.
  • Standardized styling of form elements.
  • Has/Contains Selectors.
  • Transitions to auto dimensions.
  • Fixed up handling of viewport units.

And we’ve got the vast majority of those under wraps! We have ways to query parents and containers. We’re exploring stylable selects and field-sizing. We know about :has() and we’re still going gaga over transitions to intrinsic sizes. We’ve openly opined whether there’s too much CSS (there isn’t).

But what else is on your CSS wishlist? Ironically enough, Adam Argyle went through this exercise just this morning and I love the way he’s broken things down into a user-facing wishlist and a developer-facing wishlist. I mean, geez, a CSS carousel? Yes, please! I love his list and all lists like it.

We’ll round things up and put a list together — so let us know!


What ELSE is on your CSS wishlist? originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

How to use framed photographs to improve an interior?

Post pobrano z: How to use framed photographs to improve an interior?

A boring interior can be transformed into something special with just a few moves. Framed prints can play a role in the process. In this article, we share 3 ideas on how to do it right.

Industrial interior with a classy touch

The power of minimalism and naked bricks – that’s what usually distinguishes industrial interiors. There are thousands of bars, restaurants, and offices to prove it. Many people also like to arrange their houses in this manner. No wonder, the idea is both trendy and practical. It requires some wall art, nonetheless.

Black-and-white framed prints with gallery quality will certainly fill up the blank spots. In a way, they can work as windows. Raw, desert landscape photography with massive skies and big contrast values – no better solution to bring those bricks to life. Just a handful of such custom-framed prints will be enough to add a lot of class to any industrial interior.

Family colors in the office

By visiting CANVASDISCOUNT.com, everyone can order framed photo prints from their private collections. That provides an opportunity to fill the whole office wall with family portraits. Colorful, full of smiles, and very heartwarming pictures in different sizes. That kind of decoration is suitable for business executives who like to emphasize family values and remind others what is truly important. Additionally, the arrangement of framed prints on an entire wall space improves the company image simply by humanizing it. This type of thinking in corporate interior design becomes more and more popular.

Framed prints for the bathroom

Art prints in wet rooms are nothing new. But, in most cases, they don’t cooperate together. That’s because people combine them randomly. It’s much better to give it some thought earlier. A blue, full of light bathroom will gain a lot with black-and-white framed pictures of a seascape. Contrast plays the role here, and working with it can be very beneficial.

Following that path, a rustic bathroom with wooden furniture can be visually improved by adding vivid red elements, for example. An interesting idea is to use different animal images and apply a strong red filter, which can be done with a simple photo editor. With the right frame styles, such prints will stimulate senses like a fine art gallery.

These easy-to-make interior improvements are just a fraction of possibilities available to basically everyone. In fact, all of this can be arranged via the internet. One of the moves requires visiting the webpage mentioned earlier. With such services, it seems, there’s no need for expensive decor shops anymore.

The post How to use framed photographs to improve an interior? appeared first on Designer Daily: graphic and web design blog.

How to use framed photographs to improve an interior?

Post pobrano z: How to use framed photographs to improve an interior?

A boring interior can be transformed into something special with just a few moves. Framed prints can play a role in the process. In this article, we share 3 ideas on how to do it right.

Industrial interior with a classy touch

The power of minimalism and naked bricks – that’s what usually distinguishes industrial interiors. There are thousands of bars, restaurants, and offices to prove it. Many people also like to arrange their houses in this manner. No wonder, the idea is both trendy and practical. It requires some wall art, nonetheless.

Black-and-white framed prints with gallery quality will certainly fill up the blank spots. In a way, they can work as windows. Raw, desert landscape photography with massive skies and big contrast values – no better solution to bring those bricks to life. Just a handful of such custom-framed prints will be enough to add a lot of class to any industrial interior.

Family colors in the office

By visiting CANVASDISCOUNT.com, everyone can order framed photo prints from their private collections. That provides an opportunity to fill the whole office wall with family portraits. Colorful, full of smiles, and very heartwarming pictures in different sizes. That kind of decoration is suitable for business executives who like to emphasize family values and remind others what is truly important. Additionally, the arrangement of framed prints on an entire wall space improves the company image simply by humanizing it. This type of thinking in corporate interior design becomes more and more popular.

Framed prints for the bathroom

Art prints in wet rooms are nothing new. But, in most cases, they don’t cooperate together. That’s because people combine them randomly. It’s much better to give it some thought earlier. A blue, full of light bathroom will gain a lot with black-and-white framed pictures of a seascape. Contrast plays the role here, and working with it can be very beneficial.

Following that path, a rustic bathroom with wooden furniture can be visually improved by adding vivid red elements, for example. An interesting idea is to use different animal images and apply a strong red filter, which can be done with a simple photo editor. With the right frame styles, such prints will stimulate senses like a fine art gallery.

These easy-to-make interior improvements are just a fraction of possibilities available to basically everyone. In fact, all of this can be arranged via the internet. One of the moves requires visiting the webpage mentioned earlier. With such services, it seems, there’s no need for expensive decor shops anymore.

The post How to use framed photographs to improve an interior? appeared first on Designer Daily: graphic and web design blog.

Important Features that Every Business Website Should Have

Post pobrano z: Important Features that Every Business Website Should Have

Having a well-designed website is essential for every business. It’s often the first place customers visit to learn more about your services, products, or brand. A good website isn’t just about looking professional. It should also be easy to use and create a great experience for visitors.

But what makes a business website truly effective? There are certain key features that every website needs to help it stand out, build trust, and support business growth. In this post, we’ll look at the most important features and explain why they matter.

Clear Navigation

Clear navigation is one of the most important parts of any business website. When visitors come to your site, they should be able to find what they’re looking for quickly and easily.

A simple menu at the top of the page is a great way to help people move around your website. It should include links to key pages, like your services, contact information, and about us section. Drop-down menus can also be useful if you have lots of pages, but keep them organised to avoid confusion.

Clear navigation not only improves the user experience but also helps search engines understand your website better. This can improve your rankings and make it easier for customers to find you online.

A well-organised website keeps visitors happy and increases the chance they’ll stay longer or get in touch with your business.

Mobile-Friendly Design

A mobile-friendly design is essential for any business website. More people are using their phones and tablets to browse the internet, so your site needs to look good and work well on smaller screens.

A mobile-friendly website adjusts automatically to fit any screen size. This means the text is easy to read, buttons are simple to click, and pages load quickly. If your site isn’t mobile-friendly, visitors might leave because it’s hard to use, and this can hurt your business.

Fast Loading Speed

A fast-loading website is crucial for keeping visitors happy. If your pages take too long to load, people might leave and look elsewhere. Most users expect a website to load in just a few seconds, so every second counts.

Slow websites can also affect your search engine ranking, as search engines like Google prioritise sites that load quickly. This means a fast website isn’t just good for your visitors. It’s also important for helping people find you online.

To improve loading speed, you can optimise your images, reduce large files, and use reliable hosting. A well-maintained website will not only load faster but also provide a better experience for your visitors.

Contact Information

Making it easy for visitors to contact you is essential for any business website. Your contact details, like a phone number, email address, and location, should be clearly displayed. Most websites include this information on a contact page and in the footer of every page.

Adding a contact form is also a great way to make it simple for visitors to reach you. It’s quick, easy to use, and removes the need for visitors to copy and paste your email address.

Clear and accessible contact details show your business is open and ready to help, which can encourage people to get in touch.

Trustworthy Signals

Building trust is a key part of creating a successful website. Visitors are more likely to stay and interact with your business if they feel confident in what you offer.

Adding customer reviews, testimonials, or case studies can show others have had good experiences with your business. Trust badges, like logos of certifications or memberships, can also help build credibility.

It’s important to have an SSL certificate for your website. This ensures your site is secure and shows visitors a padlock icon in their browser. A secure website reassures users, especially if they’re entering personal details or making a payment.

Search Engine Optimisation (SEO)

Search Engine Optimisation (SEO) helps your website appear higher in search engine results, making it easier for people to find you online. Good SEO ensures your site is visible when potential customers search for products or services you offer.

Basic SEO features include using the right keywords in your content, adding meta descriptions to your pages, and including alt text for images. These help search engines understand your website better and improve its ranking.

Conclusion

A successful business website isn’t just about looking good—it needs to include key features that make it easy to use, secure, and visible online. From clear navigation and mobile-friendly design to fast loading speed and SEO, these features work together to create a site that helps your business grow.

Investing in a website with these essentials, you can build trust with your visitors, attract more customers, and give your business the best chance of success.

The post Important Features that Every Business Website Should Have appeared first on Designer Daily: graphic and web design blog.

Important Features that Every Business Website Should Have

Post pobrano z: Important Features that Every Business Website Should Have

Having a well-designed website is essential for every business. It’s often the first place customers visit to learn more about your services, products, or brand. A good website isn’t just about looking professional. It should also be easy to use and create a great experience for visitors.

But what makes a business website truly effective? There are certain key features that every website needs to help it stand out, build trust, and support business growth. In this post, we’ll look at the most important features and explain why they matter.

Clear Navigation

Clear navigation is one of the most important parts of any business website. When visitors come to your site, they should be able to find what they’re looking for quickly and easily.

A simple menu at the top of the page is a great way to help people move around your website. It should include links to key pages, like your services, contact information, and about us section. Drop-down menus can also be useful if you have lots of pages, but keep them organised to avoid confusion.

Clear navigation not only improves the user experience but also helps search engines understand your website better. This can improve your rankings and make it easier for customers to find you online.

A well-organised website keeps visitors happy and increases the chance they’ll stay longer or get in touch with your business.

Mobile-Friendly Design

A mobile-friendly design is essential for any business website. More people are using their phones and tablets to browse the internet, so your site needs to look good and work well on smaller screens.

A mobile-friendly website adjusts automatically to fit any screen size. This means the text is easy to read, buttons are simple to click, and pages load quickly. If your site isn’t mobile-friendly, visitors might leave because it’s hard to use, and this can hurt your business.

Fast Loading Speed

A fast-loading website is crucial for keeping visitors happy. If your pages take too long to load, people might leave and look elsewhere. Most users expect a website to load in just a few seconds, so every second counts.

Slow websites can also affect your search engine ranking, as search engines like Google prioritise sites that load quickly. This means a fast website isn’t just good for your visitors. It’s also important for helping people find you online.

To improve loading speed, you can optimise your images, reduce large files, and use reliable hosting. A well-maintained website will not only load faster but also provide a better experience for your visitors.

Contact Information

Making it easy for visitors to contact you is essential for any business website. Your contact details, like a phone number, email address, and location, should be clearly displayed. Most websites include this information on a contact page and in the footer of every page.

Adding a contact form is also a great way to make it simple for visitors to reach you. It’s quick, easy to use, and removes the need for visitors to copy and paste your email address.

Clear and accessible contact details show your business is open and ready to help, which can encourage people to get in touch.

Trustworthy Signals

Building trust is a key part of creating a successful website. Visitors are more likely to stay and interact with your business if they feel confident in what you offer.

Adding customer reviews, testimonials, or case studies can show others have had good experiences with your business. Trust badges, like logos of certifications or memberships, can also help build credibility.

It’s important to have an SSL certificate for your website. This ensures your site is secure and shows visitors a padlock icon in their browser. A secure website reassures users, especially if they’re entering personal details or making a payment.

Search Engine Optimisation (SEO)

Search Engine Optimisation (SEO) helps your website appear higher in search engine results, making it easier for people to find you online. Good SEO ensures your site is visible when potential customers search for products or services you offer.

Basic SEO features include using the right keywords in your content, adding meta descriptions to your pages, and including alt text for images. These help search engines understand your website better and improve its ranking.

Conclusion

A successful business website isn’t just about looking good—it needs to include key features that make it easy to use, secure, and visible online. From clear navigation and mobile-friendly design to fast loading speed and SEO, these features work together to create a site that helps your business grow.

Investing in a website with these essentials, you can build trust with your visitors, attract more customers, and give your business the best chance of success.

The post Important Features that Every Business Website Should Have appeared first on Designer Daily: graphic and web design blog.

Fluid Superscripts and Subscripts

Post pobrano z: Fluid Superscripts and Subscripts

Superscripts and subscripts are essential elements in academic and scientific content — from citation references to chemical formulas and mathematical expressions. Yet browsers handle these elements with a static approach that can create significant problems: elements become either too small on mobile devices or disproportionately large on desktop displays.

After years of wrestling with superscript and subscript scaling in CSS, I’m proposing a modern solution using fluid calculations. In this article, I’ll show you why the static approach falls short and how we can provide better typography across all viewports while maintaining accessibility. Best of all, this solution requires nothing but clean, pure CSS.

The problem with static scaling

The scaling issue is particularly evident when comparing professional typography with browser defaults. Take this example (adapted from Wikipedia), where the first “2” is professionally designed and included in the glyph set, while the second uses <sub> (top) and <sup> (bottom) elements:

Diagramming the typographic parts and spacing of subscripts and superscripts.

Browsers have historically used font-size: smaller for <sup> and <sub> elements, which translates to roughly 0.83x scaling. While this made sense in the early days of CSS for simple documents, it can create problems in modern responsive designs where font sizes can vary dramatically. This is especially true when using fluid typography, where text sizes can scale smoothly between extremes.

Fluid scaling: A better solution

I’ve developed a solution that scales more naturally across different sizes by combining fixed and proportional units. This approach ensures legibility at small sizes while maintaining proper proportions at larger sizes, eliminating the need for context-specific adjustments.

CodePen Embed Fallback

Here’s how it works:

sup, sub {
  font-size: calc(0.5em + 4px);
  vertical-align: baseline;
  position: relative; 
  top: calc(-0.5 * 0.83 * 2 * (1em - 4px)); 
  /* Simplified top: calc(-0.83em + 3.32px) */
}

sub {
  top: calc(0.25 * 0.83 * 2 * (1em - 4px)); 
  /* Simplified top: calc(0.42em - 1.66px) */
}
  • Natural scaling: The degressive formula ensures that superscripts and subscripts remain proportional at all sizes
  • Baseline alignment: By using vertical-align: baseline and relative positioning, we prevent the elements from affecting line height and it gives us better control over the offset to match your specific needs. You’re probably also wondering where the heck these values come from — I’ll explain in the following.

Breaking down the math

Let’s look at how this works, piece by piece:

Calculating the font size (px)

At small sizes, the fixed 4px component has more impact. At large sizes, the 0.5em proportion becomes dominant. The result is more natural scaling across all sizes.

sup, sub {
  font-size: calc(0.5em + 4px);
  /* ... */
}

sub { 
  /* ... */
}

Calculating the parent font size (em)

Within the <sup> and <sub> elements, we can calculate the parent’s font-size:

sup, sub {
  font-size: calc(0.5em + 4px);
  top: calc(2 * (1em - 4px));
}

sub { 
  top: calc(2 * (1em + 4px));
}

The fluid font size is defined as calc(0.5em + 4px). To compensate for the 0.5em, we first need to solve 0.5em * x = 1em which gives us x = 2. The 1em here represents the font size of the <sup> and <sub> elements themselves. We subtract the 4px fixed component from our current em value before multiplying.

The vertical offset

For the vertical offset, we start with default CSS positioning values and adjust them to work with our fluid scaling:

sup, sub {
  font-size: calc(0.5em + 4px);
  top: calc(-0.5 * 0.83 * 2 * (1em - 4px));
}

sub { 
  top: calc(0.25 * 0.83 * 2 * (1em - 4px));
}

The formula is carefully calibrated to match standard browser positioning:

  • 0.5em (super) and 0.25em (sub) are the default vertical offset values (e.g. used in frameworks like Tailwind CSS and Bootstrap).
  • We multiply by 0.83 to account for the browser’s font-size: smaller scaling factor, which is used per default for superscript and subscript.

This approach ensures that our superscripts and subscripts maintain familiar vertical positions while benefiting from improved fluid scaling. The result matches what users expect from traditional browser rendering but scales more naturally across different font sizes.

Helpful tips

The exact scaling factor font-size: (0.5em + 4px) is based on my analysis of superscript Unicode characters in common fonts. Feel free to adjust these values to match your specific design needs. Here are a few ways how you might want to customize this approach:

For larger scaling:

sup, sub {
  font-size: calc(0.6em + 3px);
  /* adjust offset calculations accordingly */
}

For smaller scaling:

sup, sub {
  font-size: calc(0.4em + 5px);
  /* adjust offset calculations accordingly */
}

For backward compatibility, you might want to wrap all of it in a @supports block:

@supports (font-size: calc(1em + 1px)) {
  sup, sub {
    ...
  }
}

Final demo

I built this small interactive demo to show different fluid scaling options, compare them to the browser’s static scaling, and fine-tune the vertical positioning to see what works best for your use case:

CodePen Embed Fallback

Give it a try in your next project and happy to hear your thoughts!


Fluid Superscripts and Subscripts originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

7 Signs It’s Time To Hire a PPC Agency

Post pobrano z: 7 Signs It’s Time To Hire a PPC Agency

PPC advertising can help you attract visitors to your website, enhance conversions, and fuel your business growth. When handled well, PPC ads can show solid results, as they focus on the right audience and make the most of your ad budget. However, running successful PPC campaigns requires expertise, time, and an in-depth understanding of ever-changing trends, which can be challenging for businesses to handle alone.

If you’re wondering if it might be time to get expert help and hire a PPC agency, here are the key signs to help you decide.

1. Your Campaigns Aren’t Delivering Results

If your PPC campaigns are running, but you’re not seeing the results you expected, it’s a clear indication that something’s wrong. Factors like poor ad placement, low-quality scores, or ineffective keyword research could be holding your campaigns back. A PPC agency brings expertise in PPC advertising services, including conversion tracking, bid optimization, and landing page optimization, to help you achieve better performance.

Hiring the right PPC agency ensures you’ll have access to PPC specialists who understand how to refine your ad targeting, improve click-through rates, and optimize landing pages for maximum conversions. They also use advanced PPC tools and industry best practices to turn underperforming PPC ads into successful ones.

2. You’re Spending Too Much Without Seeing ROI

Managing your own PPC advertising can sometimes mean you’re pouring money into campaigns without seeing a return on investment. Poor bid optimization or targeting the wrong audience often results in wasted budgets. This is where a pay-per-click marketing expert can make all the difference.

A skilled PPC marketing agency focuses on key factors like cost per click, conversion rate optimization, and in-depth keyword research to reduce unnecessary ad spending while boosting ROI. Whether it’s Google Ads, Bing Ads, or paid social campaigns on platforms like Facebook, an experienced PPC team can help you achieve measurable results and control costs effectively.

3. You’re Struggling to Keep Up with PPC Trends

PPC advertising evolves rapidly, and staying updated on PPC trends, ad network changes, and new features like Google Shopping or the Google Display Network can be overwhelming. If you lack the time or expertise to keep up, a PPC management agency can help.

Top PPC agencies specialize in optimizing marketing campaigns on an ongoing basis. They understand the importance of compelling ad copy, quality ad groups, and remarketing strategies to boost conversion rates and reach the right audience. Their background in social media marketing, search engine marketing, and display advertising keeps your campaigns ahead of the competition.

4. Your Business Needs a Scalable Solution

As your small business grows, managing PPC campaigns becomes more complex. A growing business might require additional ad placements across various channels like social media platforms, display ads, and search engines. Whether you’re targeting home services in New York or scaling up nationwide in the United States, a great PPC agency can adapt to your needs.

With professional PPC consulting, a service marketing agency helps create scalable solutions tailored to your growth. They integrate PPC advertising with social media management and search engine optimization to enhance your overall digital marketing strategy.

5. Your Internal Team Lacks Expertise

If your in-house team doesn’t have a PPC expert, your campaigns might suffer from inefficiencies like low-quality scores or missed opportunities for remarketing. A dedicated PPC specialist from a digital marketing agency can fill this gap. They’ll manage all aspects of your campaigns, from keyword research to bid optimization, ensuring your PPC advertising delivers accurate results.

By partnering with a PPC agency, you gain access to professionals who excel in PPC management services, ad network expertise, and conversion tracking. Their knowledge ensures your marketing campaigns are expertly handled, leaving you free to focus on other aspects of your business.

6. You Need Consistent Performance Monitoring

PPC campaigns require constant analysis and refinement to stay effective. If you’re not tracking your conversion rates, ad targeting, and click-through rates regularly, you’re likely missing out on optimization opportunities. A reliable PPC marketing agency provides ongoing optimization to ensure your ads maintain peak performance.

They use PPC tools to monitor your campaigns, make adjustments, and deliver measurable results. From landing page optimization to Facebook Ads and Bing Ads, a skilled PPC team ensures every campaign provides maximum ROI.

7. You Want to Expand Your Marketing Efforts

Expanding into new ad networks or running campaigns on multiple social media platforms requires expertise. A professional PPC agency can help you diversify your marketing efforts with Google Ads, Bing Ads, display advertising, and paid search campaigns. They also integrate these efforts with social media platforms like Facebook to create a holistic strategy.

With their expert guidance, you can explore new opportunities for niche industries or geographic locations, such as Google Shopping, remarketing, and campaign management.

Conclusion

Managing PPC campaigns effectively takes time, skill, and resources. If any of these signs resonate with you, partnering with a PPC marketing agency could be the solution to unlocking your campaigns’ full potential. By leveraging their expertise in PPC management, you’ll see improvements in ad performance, conversion rates, and overall ROI.

Whether you’re a small business or a large organization, hiring the right PPC agency can transform your approach to digital marketing and deliver the actual results you’ve been looking for.

The post 7 Signs It’s Time To Hire a PPC Agency appeared first on Designer Daily: graphic and web design blog.