Razvan Zamfir
Razvan Zamfir

Reputation: 4616

Svelte application bug: global.css overwrites component-level CSS

In a Svelte app, I have a form with simple validation.

The form

<form id="surveyForm" class="mt-4">
    <div class="form-group">
      <input type="text" class="form-control" placeholder="First name">
    </div>
    <div class="form-group">
      <input type="text" class="form-control" placeholder="Last name">
    </div>
    <button class="btn btn-full" on:click={Continue}>Continue</button>
</form>

The validation:

import {fly, fade} from 'svelte/transition';
let hasError = false;
let errMessage = "";
let isSuccessVisible = false;

function validateInput() {
    var surveyForm = document.getElementById('surveyForm'),
        inputFields = surveyForm.querySelectorAll('input[type=text]');

    hasError = false;

    inputFields.forEach(function(field) {
        field.classList.remove("is-invalid");
        var inputFieldVal = field.value;
        if (inputFieldVal.length == 0) {
            field.classList.add("is-invalid");
            hasError = true;
        }
    });
    errMessage = "All the fileds are mandatory";
}


function Continue(e) {
    e.preventDefault();
    validateInput();
    if (hasError == false) {
        isSuccessVisible = true;

        setTimeout(function() {
            isSuccessVisible = false;
        }, 3000);
    }
}

In order to give invalid form fields a red border, I use border: 1px solid #c00 !important:

.error-alert {
    border: 1px solid #c00 !important;
    color: #c00;
}

The class is-invalid is correctly added (and removed), yet, the default global.css styles overwrite my component-level styles, as you can see in this REPL.

Why does this happen? What is a reliable fix?


NOTE: The case described is a simplified one, in reality I might need other validations, like making sure the value is numeric. The solution I have to implement is not a very specific one.

Upvotes: 1

Views: 1463

Answers (2)

digby280
digby280

Reputation: 899

You can use the technique Rich described for other validations by using HTML5 client-side form validation. More information can be found on the MDN

<form id="surveyForm" class="mt-4" class:submitted>
    <div class="form-group">
    <input type="text" class="form-control" placeholder="First name" required>
  </div>
    <div class="form-group">
    <input type="text" class="form-control" placeholder="Last name" required>
  </div>
    <div class="form-group">
    <input type="number" class="form-control" placeholder="Enter a number" required>
  </div>
    <div class="form-group">
    <input type="email" class="form-control" placeholder="Email" required>
  </div>
    <div class="form-group">
    <input type="date" class="form-control" placeholder="Date" required>
  </div>
  <button class="btn btn-full" on:click|preventDefault={Continue}>Continue</button>
</form>

REPL

If you don't want to use this - or it doesn't cover all your use cases for some reason - you can manipulate the classes using Svelte instead of accessing the DOM directly:

<div class="form-group">
    <input bind:value={second} type="text" class="form-control" class:is-invalid="{ submitted && second.length == 0 }" placeholder="Last name">
</div>

REPL

The reason Svelte removes your .is-invalid style is because Svelte doesn't use it. I wouldn't recommend it in this case but you can force Svelte to keep the style using :global():

:global(.is-invalid) {
    border: 1px solid #c00;
}

REPL

Upvotes: 2

Rich Harris
Rich Harris

Reputation: 29605

In the REPL, you see the following warning:

Unused CSS selector (87:1)

That's because the .is-invalid selector isn't used anywhere in the markup — if a classname is unused, Svelte tells you about it and removes it. For Svelte to be able to 'see' it, it has to be in the markup — you can't add it programmatically with classList. (This is a feature, not a bug: programmatically manipulating the DOM will inevitably lead to your view getting out of sync with your state.)

In this case though, you don't need to add a class to the input element, or do the checking inside a function. You can just use the input's required attribute and :invalid CSS selector:

<input type="text" class="form-control" placeholder="First name" required>
input:invalid {
  border: 1px solid #c00;
}

The :invalid state will be applied before the user submits any data though, so you may want to add a class that indicates whether the user has tried to submit already:

<form id="surveyForm" class="mt-4" class:submitted>
  <div class="form-group">
    <input type="text" class="form-control" placeholder="First name" required>
  </div>

  <!-- ... -->
</form>
.submitted input:invalid {
  border: 1px solid #c00;
}

The easiest way to add that class is in a click event handler on the button.

Here's a demo.

Using the stuff you get for free in HTML comes with other benefits. For example, the form won't submit as long as there are validation errors (and validation isn't limited to 'required' — you can mandate specific patterns, or use e.g. type="email" to require a valid email address, etc), which means that inside your submit event handler, you already know that the inputs are valid.

Upvotes: 5

Related Questions