HTML/CSS forms: failing to align form's input fields by different means

I don’t know, maybe, I’m asking something obvious, but I rarely address front-end side of projects, so this mistake is new for me.

I have a site made with Django and on this site I have a page with a register form. I want all rows of it to be organised one under another, aligned by the beginning of their input fields.

The file structure of the project (maybe the problem lies here):

django_project
|-.venv/.idea/...
|-.django_project
 |-static
  |-css
   |-styles.css
 |-templates
  |-inc
  |-base.html
 |-asgi/settings.py/urls.py/wsgi.py
|-usersapp
 |-migrations
 |-templates
  |-usersapp
   |-login.html
   |-register.html
   |-logout.html
   |-(and so on)
 |-admin.py/apps.py/consts.py/forms.py/models.py/tests.py/urls.py/views.py

Corresponding form class from forms.py:

class RegisterUserForm(UserCreationForm):
    username = forms.CharField(label="Имя пользователя", widget=forms.TextInput(attrs={'class': 'form-input'}))
    password1 = forms.CharField(label="Пароль", widget=forms.PasswordInput(attrs={'class': 'form-input'}))
    password2 = forms.CharField(label="Повторно введите пароль", widget=forms.PasswordInput(attrs={'class': 'form-input'}))

    class Meta:
        model = get_user_model()
        fields = ['username', 'email', 'first_name', 'last_name', 'password1', 'password2']
        labels = {
            'email': 'E-mail',
            'last_name': 'Фамилия',
            'first_name': 'Имя',
            'patronymic': 'Отчество'
        }
        widgets = {
            'email': forms.TextInput(attrs={'class': 'form-input'}),
            'last_name': forms.TextInput(attrs={'class': 'form-input'}),
            'first_name': forms.TextInput(attrs={'class': 'form-input'}),
            'patronymic': forms.TextInput(attrs={'class': 'form-input'})
        }

        def clean_email(self):
            email = self.cleaned_data['email']
            if get_user_model().objects.filter(email=email).exists():
                raise forms.ValidationError("Такой E-mail уже существует!")
            return email

The register form template (register.html):

{% extends 'base.html' %}

{% block content %}
<h1>Register</h1>

<div class="form-container">
    <div class="row">
        <form method="post">
            {% csrf_token %}
            <input type="hidden" name="next" value="{{ next }}" />
            <div class="form-error">{{ form.non_field_errors }}</div>
            {% for f in form %}
            <p><label for="{{ f.id_for_label }}">{{ f.label }}</label>{{ f }}</p>
            <div class="form-error">{{ f.errors }}</div>
            {% endfor %}
            <p><button type="submit">Register</button></p>
        </form>
    </div>
</div>
{% endblock %}

base.html:

{% load static %}

<!DOCTYPE html>
<html>
<head>
    <link type="text/css" href="{% static 'css/styles.css' %}" rel="stylesheet"/>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>{{title}}</title>
</head>
<body id="third">
    <header>
        {% include 'inc/_timedate.html' %}
        {% include 'inc/auth.html' %}
        {% include 'inc/_navbar.html' %}
    </header>
{% block content %}

{% endblock %}

{% block pagination %}
{% endblock %}

</body>
</html>

Corresponding CSS classes:

<...(CSS resets and so on)>
#third {
  background-color: #1B182B;
  background-image: url("{% static 'media/cdwdi.webp' %}");
  background-size: 1370px 610px;
  background-position: 50% 0;
  background-repeat: no-repeat;
}
<...(navbar styles and so on)>
.container-posts {
  margin-left: 50px;
  margin-top: 10px;
  padding: 5px 5px 5px 5px;
  color: white;
}
.form-container {
  display: grid;
  grid-template-columns: 100px 1fr;
  align-items: center;;
  gap: 15px 10px;
  max-width: 400px;
  margin-left: 50px auto;
  margin-top: 10px;
  padding: 5px 5px 5px 5px;
  border-radius: 8px;
  color: white;
}
.row {
  margin-top: 20px;
}

If to put a container-posts class in the first div block, it kind of works - it aligns rows, though not by the center of their input fields, but by the beginning of their labels. container-posts class wasn’t designed to style the forms, so it shouldn’t work here the way I expect it to.

But if to put a form-container class in the first div block, styles cease to work for the whole form (text turns black, labels slide to the left edge of a page, no alignment at all). I’ve googled fot it and tried to code it from grid, flex and other display layouts, but with no success.

That’s why I humbly ask for your advice, and, if possible, an explanation why does your solution work this way. If any additional information is required to make the problem clear, please tell me and I will provide it.

Although this isn’t a Django problem, I started trying to help, but I realised I don’t know what you mean by this:

Can you draw a simple diagram of how you want the rows, labels and input fields to be laid out?

Mr. Phil Gyford,
Definitely, I’ll try my best.
That’s what I want to achieve:
([ ________ ] - is an input field)

  username: [________]
  password: [________]
    e-mail: [________]
 last name: [________]
first name: [________]

Here’s what container-posts class of thediv block does:

(padding works, font color works)
   username: [________]
   password: [________]
   e-mail: [________]
   last name: [________]
   first name: [________]

And here’s what form-container class, which is intended to process the form, does with it:

(padding inactive, font color doesn’t work (stays black))
username: [________ ]
password: [________ ]
e-mail: [ ________ ]
last name: [ ________ ]
first name: [ ________ ]

So, while container-posts doesn’t align rows by the beginning of their input fields, at least it makes padding, font color and other settings work. While in case of form-container, it seems, style settings aren’t applied to the form at all.

While I presume this problem isn’t connected with Django, still I noticed that the way I provided form’s rows to the template is designed to be suitable for the Django forms. When the form is coded the way like:

<form>
  <label for=...>
  <input type=...>
  <label for=...>
  <input type=...>
  (and so on)
</form>

then my styling works properly.

Thank you for your eagerness to help me.

I’ve put an example of how you could do this using flexbox in this codepen, but for completeness here’s the HTML and CSS from it:

<form method="post">
  <input type="hidden" name="next" value="{{ next }}" />
  <div class="form-error">Errors here</div>
  <div class="form-row">
    <label for="input1">My first label which is long</label>
    <div class="form-input">
      <input type="text" id="input1">
    </div>
  </div>
  <div class="form-row">
    <label for="input2">Second label</label>
    <div class="form-input">
      <input type="text" id="input2">
    </div>
  </div>
  <div class="form-error">More errors here</div>
  <p><button type="submit">Register</button></p>
</form>
.form-row {
  display: flex;
  margin-bottom: 20px;
}
.form-row label {
  width: 120px;
  text-align: right;
  margin-right: 10px
}
.form-input {
  width: 200px;
}
.form-input input {
  width: 100%;
}

And it looks like this:

So to adapt that HTML for your template:

{% extends 'base.html' %}

{% block content %}
<h1>Register</h1>

<div class="form-container">
  <form method="post">
    {% csrf_token %}
    <input type="hidden" name="next" value="{{ next }}" />
    <div class="form-error">{{ form.non_field_errors }}</div>
    {% for f in form %}
      <div class="form-row">
        <label for="{{ f.id_for_label }}">{{ f.label }}</label>
        <div class="form-input">
          {{ f }}
          <div class="form-error">{{ f.errors }}</div>
        </div>  
      </div>
    {% endfor %}
    <p><button type="submit">Register</button></p>
  </form>
</div>
{% endblock %}

Notes:

  1. I renamed .row to .form-row so it’s a bit more specific, and moved it within the for loop.
  2. .form-container isn’t used in my styles so you could do without it unless you want to apply styling to the outside of the form
  3. The extra .form-input div is required to keep things lined up.
  4. You’ll need to loop through both form.non_field_errors and f.errors to display each error separately if you want to display them nicely - I’ll leave that to you!

For things like this I often find it useful to look at a CSS framework and see how they do it. For example, Bootstrap 5’s horizontal forms.

1 Like

Mr. Phil Gyford,
Thank you very much, I’m going to try that now!

Concerning the last note - do you mean that for both form.non_field_errors and f.errors I have to make a separate loop; or do they have to be included into the main loop (with the form)?

As it is the errors will be displayed but they might not look very good. If you want more control you’ll need to have separate loops for them. See this for example Working with forms | Django documentation | Django

So you could do this for the non_field_errors:

{% for error in form.non_field_errors %}
  <p>{{ error }}</p>
{% endfor %}

And similar for the field errors:

{% for error in f.errors %}
  <p>{{ error }}</p>
{% endfor %}

Mr. Phil Gyford,
I’ve just tried the css/html code you’ve proposed,
and it somehow solves the problem with alignment (it doesn’t align the way I want it to, but at least it has changed - now labels go above their corresponding input fields, and all of them are aligned by the left edge of a page).
But the global problem still exists.

Here are changes I’ve introduced thanks to your help:

html:

{% extends 'base.html' %}

{% block content %}
<h1>Register</h1>

<div class="form-container">
  <form method="post">
    {% csrf_token %}
    <input type="hidden" name="next" value="{{ next }}" />
    <div class="form-error">
    {% for error in form.non_field_errors %}
        <p>{{ error }}</p>
    {% endfor %}
    </div>
    {% for f in form %}
      <div class="form-row">
        <label for="{{ f.id_for_label }}">{{ f.label }}</label>
        <div class="form-input">
          {{ f }}
          <div class="form-error">{{ f.errors }}</div>
        </div>
      </div>
    {% endfor %}
    <p><button type="submit">Register</button></p>
  </form>
</div>
{% endblock %}

css:

<...>
.container-posts {
  margin-left: 50px;
  margin-top: 10px;
  padding: 5px 5px 5px 5px;
  color: white;
}
.form-container {
  max-width: 800px;
  margin-left: 50px auto;
  margin-top: 20px;
  padding: 5px 5px 5px 5px;
  border-radius: 8px;
  color: white;
}
.form-row {
  display: flex;
  margin-left: 20px;
  margin-bottom: 20px;
  color: white;
}
.form-row label {
  width: 200px;
  text-align: right;
  margin-right: 10px;
  color: white;
}
.form-input {
  width: 200px;
}
.form-input input {
  width: 100%;
}

The current form implementation is as follows:

(font style isn't applied, margin/padding aren't applied)
Register

username:
[________]
password:
[________]
e-mail:
[________]
last name:
[________]
first name:
[________]
[Register]

I don’t know what’s going on, but for the template login.html with a similar code, having container-posts class in the biggest div block, the style setting works properly: font color becomes white, the form stops being glued to the left edge of the page (though no alignment by input field, as I wish to).
I’ve changed form-container to be merely a container style class with the simplest settings; I’ve put color: white almost everywhere to make the font color white, and yet those settings are not applied, while it works for blocks with container-posts class:
login.html

{% extends 'base.html' %}}

{% block content %}
<h1>Log in</h1>

<div class="container-posts">
    <div class="row">
        <form method="post">
            {% csrf_token %}
            <input type="hidden" name="next" value="{{ next }}" />
            <div class="form-error">{{ form.non_field_errors }}</div>
            {% for f in form %}
            <p><label class="form-label" for="{{ f.id_for_label }}">{{ f.label }}</label>{{ f }}</p>
            <div class="form-error">{{ f.errors }}</div>
            {% endfor %}
            <p><button type="submit">Log in</button></p>

            <p><a href="{% url 'users:password_reset' %}">Forgot your password?</a></p>
        </form>
    </div>
</div>
{% endblock %}

Do you have any ideas why do those two similar classes work so differently (one works, another doesn’t)?
Pictures of the actual situation:


I tried out your first example of HTML with the CSS in Codepen and it looks fine https://codepen.io/philgyford/pen/EaxpqXg?editors=1100

So there is either something different in your template compared to the code you supplied, or there is other CSS affecting the layout. You’ll need to use your browser’s Inspector panel to look at various HTML elements in the page to see what CSS is affecting them.

As for why the two pages are different… I haven’t tried the login page HTML, but I can see some differences in the HTML and CSS classes – if you want them to appear the same, you’ll have to ensure they have more identical HTML.

Mr. Phil Gyford,
I tried to inspect the register page.

It shows that display: flex setting haven’t been applied to form-row div blocks. But I have no idea how to find which CSS style rule affects it.
In CSS file, before all rules, I have a huge portion of CSS reset:

html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
  margin: 0;
  padding: 0;
  border: 0;
  font-size: 100%;
  font: inherit;
  vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
  display: block;
}
body {
  line-height: 1;
}
ol, ul {
  list-style: none;
}
blockquote, q {
  quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
  content: '';
  content: none;
}
table {
  border-collapse: collapse;
  border-spacing: 0;
}

I’m not sure whether this block is required or helpful; maybe the problem is connected with it?

Concerning the second point - I’ve set both form-container and container-posts identical. With the same result. Even the code of login.html and register.html is almost identical now.

You can see which style rules are applied to an element in the inspector. In your example:

You can see that the only styles applied to it are the “reset” styles from the styles.css file. It isn’t picking up any of the .form-row styles, so that page isn’t loading those styles. Are they in a different CSS file that’s not used on that page? Has that page cached an old version of the CSS file?

Without seeing the actual site it’s hard to diagnose the problem further. Although the templates are “almost identical now”, clearly there is something different on the pages, so you need to find it. Are the pages cached in the browser or in your site’s cache, and so template changes aren’t loaded in the browser? Are both pages actually loading the correct CSS file and does it have the latest changes?

Mr. Phil Gyford,

I’ve cleaned the browser cash now, and styles ceased to work TOTALLY. :,(

I guess it can be connected with the thing that I moved styles.css to the project’s root, away from <project_name> folder, as it used to be.

I have only one CSS-file, styles.css. I know it’s not a good practice, and it’s better to separate the functionality. The site is in Debug mode, never been in deployment yet.

Can you give me a tip concerning the css and templates positioning? If I have several apps, is it better to put both css and templates of all apps to the project’s root folder; or (vice-versa) is it better to put all styles and all templates in their corresponding app folders, having no front-end in the root?

I’ll try to properly locate all CSS and templates and try to apply styles once again and then will communicate the results here. Thank you.

It’s fine to have a single CSS file for your site. Nothing wrong with that.

The CSS file should be within your project’s static directory.

So:

  1. What is the path to where your CSS file currently is on your file system?
  2. How are you including it in your templates?
  3. What are the STATIC related settings I your settings.py file?

Mr. Phil Gyford,

  1. I have root folder and project main folder with the same name, django_project (yep, minimalistic). So, the CSS-file currently is located in D:/.../django_project/static/css/styles.css. The main folder of the project is D:/.../django_project/django_project/.

  2. I have a ‘base.html’ template in django_project/django_project/templates/base.html. Templates of all 3 apps are inherited from it. Inside base.html there is a static tag and a link in the <head> part:

{% load static %}

<!DOCTYPE html>
<html>
<head>
    <link type="text/css" href="{% static 'css/styles.css' %}" rel="stylesheet"/>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>{{title}}</title>
</head>
<...>

All other templates just start with {% extends 'base.html' %} and then go on with {% block content %}.

  1. I guess you mean these:
BASE_DIR = Path(__file__).resolve().parent.parent
<...>
STATIC_URL = 'static/'

STATICFILES_DIRS = [
    os.path.join(BASE_DIR, 'django_project', 'static/'),
]
<...>

Thank you; if any additional information is necessary, I’m ready to provide it.

I think that’s probably OK.

In your browser, open the “Network” tab of the inspector tools and refresh one of your site’s pages.

You should see a request for the styles.css file. Is that there?

If so, what is the response to the request? 200? 404? Something else?

Also, what is the URL of the styles.css as requested by the browser?

Mr. Phil Gyford,
It appeared to be Not OK, I deleted ‘django_project’ from STATICFILES_DIRS, as it pointed to the project’s main folder, not the root one, where CSS-file is located; after that the program started to locate CSS-file properly once again.

Concerning the problem with alignment, I’ve agreed with myself on display: grid option, because display: flex one stretched the form onto the whole page, putting the form into the middle, and that’s not what I wanted. Grid setting put it to the small area in the upper-left corner, as I wanted it to be, and as it is the only option worked, I agreed to leave the form the grid way (inputs under labels, not near them).

<...>
.form-container {
  margin-left: 50px;
  margin-top: 10px;
  padding: 5px 5px 5px 5px;
  color: white;
}
.form-row {
  display: grid;
  align-items: left;
  justify-content: space-between;
  margin-left: 20px;
  margin-bottom: 20px;
}
.form-row label {
  width: 200px;
  text-align: left;
  margin-right: 10px;
  color: #FFFFFF;
}
.form-input {
  width: 200px;
}
.form-input input {
  width: 100%;
}
<...>

Now I’m struggling with another problem - I’m trying to implement changeable color themes on my site, and that’s a nightmare; but, I guess, I will have to open another topic to seek help for that.
Still, thank you very much for your help, I’ll put your message with the suggested CSS-layout as a solution.