Django Tutorial Part 8: User authentication and permissions - Learn web development

Django Tutorial Part 8: User authentication and permissions
===========================================================

* Previous
* Overview: Django
* Next

In this tutorial, we'll show you how to allow users to log in to your site with their own accounts, and how to control what they can do and see based on whether or not they are logged in and their *permissions*. As part of this demonstration, we'll extend the LocalLibrary website, adding login and logout pages, and user- and staff-specific pages for viewing books that have been borrowed.

|  |  |
| --- | --- |
| Prerequisites: | Complete all previous tutorial topics, up to and including Django Tutorial Part 7: Sessions framework. |
| Objective: | To understand how to set up and use user authentication and permissions. |

Overview
--------

Django provides an authentication and authorization ("permission") system, built on top of the session framework discussed in the previous tutorial, that allows you to verify user credentials and define what actions each user is allowed to perform. The framework includes built-in models for `Users` and `Groups` (a generic way of applying permissions to more than one user at a time), permissions/flags that designate whether a user may perform a task, forms and views for logging in users, and view tools for restricting content.

**Note:** According to Django the authentication system aims to be very generic, and so does not provide some features provided in other web authentication systems. Solutions for some common problems are available as third-party packages. For example, throttling of login attempts and authentication against third parties (e.g. OAuth).

In this tutorial, we'll show you how to enable user authentication in the LocalLibrary website, create your own login and logout pages, add permissions to your models, and control access to pages. We'll use the authentication/permissions to display lists of books that have been borrowed for both users and librarians.

The authentication system is very flexible, and you can build up your URLs, forms, views, and templates from scratch if you like, just calling the provided API to log in the user. However, in this article, we're going to use Django's "stock" authentication views and forms for our login and logout pages. We'll still need to create some templates, but that's pretty easy.

We'll also show you how to create permissions, and check on login status and permissions in both views and templates.

Enabling authentication
-----------------------

The authentication was enabled automatically when we created the skeleton website (in tutorial 2) so you don't need to do anything more at this point.

**Note:** The necessary configuration was all done for us when we created the app using the `django-admin startproject` command. The database tables for users and model permissions were created when we first called `python manage.py migrate`.

The configuration is set up in the `INSTALLED_APPS` and `MIDDLEWARE` sections of the project file (**locallibrary/locallibrary/settings.py**), as shown below:

```python
INSTALLED_APPS = [
    # …
    'django.contrib.auth',  # Core authentication framework and its default models.
    'django.contrib.contenttypes',  # Django content type system (allows permissions to be associated with models).
    # …

MIDDLEWARE = [
    # …
    'django.contrib.sessions.middleware.SessionMiddleware',  # Manages sessions across requests
    # …
    'django.contrib.auth.middleware.AuthenticationMiddleware',  # Associates users with requests using sessions.
    # …
```

Creating users and groups
-------------------------

 You already created your first user when we looked at the Django admin site in tutorial 4 (this was a superuser, created with the command `python manage.py createsuperuser`).
 Our superuser is already authenticated and has all permissions, so we'll need to create a test user to represent a normal site user. We'll be using the admin site to create our *locallibrary* groups and website logins, as it is one of the quickest ways to do so.

**Note:** You can also create users programmatically as shown below.
 You would have to do this, for example, if developing an interface to allow "ordinary" users to create their own logins (you shouldn't give most users access to the admin site).

```python
from django.contrib.auth.models import User

# Create user and save to the database
user = User.objects.create_user('myusername', 'myemail@crazymail.com', 'mypassword')

# Update fields and then save again
user.first_name = 'Tyrone'
user.last_name = 'Citizen'
user.save()
```

 Note however that it is highly recommended to set up a *custom user model* when starting a project, as you'll be able to easily customize it in the future if the need arises.
 If using a custom user model the code to create the same user would look like this:

```python
# Get current user model from settings
from django.contrib.auth import get_user_model
User = get_user_model()

# Create user from model and save to the database
user = User.objects.create_user('myusername', 'myemail@crazymail.com', 'mypassword')

# Update fields and then save again
user.first_name = 'Tyrone'
user.last_name = 'Citizen'
user.save()
```

For more information, see Using a custom user model when starting a project (Django docs).

Below we'll first create a group and then a user. Even though we don't have any permissions to add for our library members yet, if we need to later, it will be much easier to add them once to the group than individually to each member.

Start the development server and navigate to the admin site in your local web browser (`http://127.0.0.1:8000/admin/`). Login to the site using the credentials for your superuser account. The top level of the Admin site displays all of your models, sorted by "Django application". From the **Authentication and Authorization** section, you can click the **Users** or **Groups** links to see their existing records.

![Admin site - add groups or users](/en-US/docs/Learn/Server-side/Django/Authentication/admin_authentication_add.png)

First lets create a new group for our library members.

1. Click the **Add** button (next to Group) to create a new *Group*; enter the **Name** "Library Members" for the group.

 ![Admin site - add group](/en-US/docs/Learn/Server-side/Django/Authentication/admin_authentication_add_group.png)
2. We don't need any permissions for the group, so just press **SAVE** (you will be taken to a list of groups).

Now let's create a user:

1. Navigate back to the home page of the admin site
2. Click the **Add** button next to *Users* to open the *Add user* dialog box.

 ![Admin site - add user pt1](/en-US/docs/Learn/Server-side/Django/Authentication/admin_authentication_add_user_prt1.png)
3. Enter an appropriate **Username** and **Password**/**Password confirmation** for your test user
4. Press **SAVE** to create the user.
 The admin site will create the new user and immediately take you to a *Change user* screen where you can change your **username** and add information for the User model's optional fields. These fields include the first name, last name, email address, and the user's status and permissions (only the **Active** flag should be set). Further down you can specify the user's groups and permissions, and see important dates related to the user (e.g. their join date and last login date).

 ![Admin site - add user pt2](/en-US/docs/Learn/Server-side/Django/Authentication/admin_authentication_add_user_prt2.png)
5. In the *Groups* section, select **Library Member** group from the list of *Available groups*, and then press the **right-arrow** between the boxes to move it into the *Chosen groups* box.

 ![Admin site - add user to group](/en-US/docs/Learn/Server-side/Django/Authentication/admin_authentication_user_add_group.png)
6. We don't need to do anything else here, so just select **SAVE** again, to go to the list of users.

That's it! Now you have a "normal library member" account that you will be able to use for testing (once we've implemented the pages to enable them to log in).

**Note:** You should try creating another library member user. Also, create a group for Librarians, and add a user to that too!

Setting up your authentication views
------------------------------------

Django provides almost everything you need to create authentication pages to handle login, log out, and password management "out of the box". This includes a URL mapper, views and forms, but it does not include the templates — we have to create our own!

In this section, we show how to integrate the default system into the *LocalLibrary* website and create the templates. We'll put them in the main project URLs.

**Note:** You don't have to use any of this code, but it is likely that you'll want to because it makes things a lot easier.
 You'll almost certainly need to change the form handling code if you change your user model, but even so, you would still be able to use the stock view functions.

**Note:** In this case, we could reasonably put the authentication pages, including the URLs and templates, inside our catalog application.
 However, if we had multiple applications it would be better to separate out this shared login behavior and have it available across the whole site, so that is what we've shown here!

### Project URLs

Add the following to the bottom of the project urls.py file (**locallibrary/locallibrary/urls.py**) file:

```python
# Add Django site authentication urls (for login, logout, password management)

urlpatterns += [
    path('accounts/', include('django.contrib.auth.urls')),
]
```

 Navigate to the `http://127.0.0.1:8000/accounts/` URL (note the trailing forward slash!).
 Django will show an error that it could not find this URL, and list all the URLs it tried.
 From this you can see the URLs that will work, for example:

**Note:** Using the above method adds the following URLs with names in square brackets, which can be used to reverse the URL mappings. You don't have to implement anything else — the above URL mapping automatically maps the below mentioned URLs.

```python
accounts/ login/ [name='login']
accounts/ logout/ [name='logout']
accounts/ password_change/ [name='password\_change']
accounts/ password_change/done/ [name='password\_change\_done']
accounts/ password_reset/ [name='password\_reset']
accounts/ password_reset/done/ [name='password\_reset\_done']
accounts/ reset/<uidb64>/<token>/ [name='password\_reset\_confirm']
accounts/ reset/done/ [name='password\_reset\_complete']
```

 Now try to navigate to the login URL (`http://127.0.0.1:8000/accounts/login/`). This will fail again, but with an error that tells you that we're missing the required template (**registration/login.html**) on the template search path.
 You'll see the following lines listed in the yellow section at the top:

```python
Exception Type:    TemplateDoesNotExist
Exception Value:    registration/login.html
```

The next step is to create a registration directory on the search path and then add the **login.html** file.

### Template directory

The URLs (and implicitly, views) that we just added expect to find their associated templates in a directory **/registration/** somewhere in the templates search path.

For this site, we'll put our HTML pages in the **templates/registration/** directory. This directory should be in your project root directory, that is, the same directory as the **catalog** and **locallibrary** folders. Please create these folders now.

**Note:** Your folder structure should now look like the below:
```

locallibrary/   # Django project folder
  catalog/
  locallibrary/
  templates/
    registration/
```

 To make the **templates** directory visible to the template loader we need to add it in the template search path.
 Open the project settings (**/locallibrary/locallibrary/settings.py**).

Then import the `os` module (add the following line near the top of the file).

```python
import os # needed by code below
```

Update the `TEMPLATES` section's `'DIRS'` line as shown:

```python
    # …
    TEMPLATES = [
      {
       # …
       'DIRS': [os.path.join(BASE_DIR, 'templates')],
       'APP\_DIRS': True,
       # …
```

### Login template

**Warning:** The authentication templates provided in this article are a very basic/slightly modified version of the Django demonstration login templates. You may need to customize them for your own use!

Create a new HTML file called /**locallibrary/templates/registration/login.html** and give it the following contents:

```django
{% extends "base\_generic.html" %}

{% block content %}

  {% if form.errors %}
    <p>Your username and password didn't match. Please try again.</p>
  {% endif %}

  {% if next %}
    {% if user.is\_authenticated %}
      <p>Your account doesn't have access to this page. To proceed,
      please login with an account that has access.</p>
    {% else %}
      <p>Please login to see this page.</p>
    {% endif %}
  {% endif %}

  <form method="post" action="{% url 'login' %}">
    {% csrf\_token %}
    <table>
      <tr>
        <td>{{ form.username.label\_tag }}</td>
        <td>{{ form.username }}</td>
      </tr>
      <tr>
        <td>{{ form.password.label\_tag }}</td>
        <td>{{ form.password }}</td>
      </tr>
    </table>
    <input type="submit" value="login">
    <input type="hidden" name="next" value="{{ next }}">
  </form>

  {# Assumes you set up the password\_reset view in your URLconf #}
  <p><a href="{% url 'password\_reset' %}">Lost password?</a></p>

{% endblock %}
```

This template shares some similarities with the ones we've seen before — it extends our base template and overrides the `content` block. The rest of the code is fairly standard form handling code, which we will discuss in a later tutorial. All you need to know for now is that this will display a form in which you can enter your username and password, and that if you enter invalid values you will be prompted to enter correct values when the page refreshes.

Navigate back to the login page (`http://127.0.0.1:8000/accounts/login/`) once you've saved your template, and you should see something like this:

![Library login page v1](/en-US/docs/Learn/Server-side/Django/Authentication/library_login.png)

If you log in using valid credentials, you'll be redirected to another page (by default this will be `http://127.0.0.1:8000/accounts/profile/`). The problem is that, by default, Django expects that upon logging in you will want to be taken to a profile page, which may or may not be the case. As you haven't defined this page yet, you'll get another error!

Open the project settings (**/locallibrary/locallibrary/settings.py**) and add the text below to the bottom. Now when you log in you should be redirected to the site homepage by default.

```python
# Redirect to home URL after login (Default redirects to /accounts/profile/)
LOGIN_REDIRECT_URL = '/'
```

### Logout template

If you navigate to the logout URL (`http://127.0.0.1:8000/accounts/logout/`) then you'll see some odd behavior — your user will be logged out sure enough, but you'll be taken to the **Admin** logout page. That's not what you want, if only because the login link on that page takes you to the Admin login screen (and that is only available to users who have the `is_staff` permission).

Create and open **/locallibrary/templates/registration/logged\_out.html**. Copy in the text below:

```django
{% extends "base\_generic.html" %}

{% block content %}
  <p>Logged out!</p>
  <a href="{% url 'login'%}">Click here to login again.</a>
{% endblock %}
```

This template is very simple. It just displays a message informing you that you have been logged out, and provides a link that you can press to go back to the login screen. If you go to the logout URL again you should see this page:

![Library logout page v1](/en-US/docs/Learn/Server-side/Django/Authentication/library_logout.png)

### Password reset templates

The default password reset system uses email to send the user a reset link. You need to create forms to get the user's email address, send the email, allow them to enter a new password, and to note when the whole process is complete.

The following templates can be used as a starting point.

#### Password reset form

This is the form used to get the user's email address (for sending the password reset email). Create **/locallibrary/templates/registration/password\_reset\_form.html**, and give it the following contents:

```django
{% extends "base\_generic.html" %}

{% block content %}
  <form action="" method="post">
  {% csrf\_token %}
  {% if form.email.errors %}
    {{ form.email.errors }}
  {% endif %}
      <p>{{ form.email }}</p>
    <input type="submit" class="btn btn-default btn-lg" value="Reset password">
  </form>
{% endblock %}
```

#### Password reset done

This form is displayed after your email address has been collected. Create **/locallibrary/templates/registration/password\_reset\_done.html**, and give it the following contents:

```django
{% extends "base\_generic.html" %}

{% block content %}
  <p>We've emailed you instructions for setting your password. If they haven't arrived in a few minutes, check your spam folder.</p>
{% endblock %}
```

#### Password reset email

This template provides the text of the HTML email containing the reset link that we will send to users. Create **/locallibrary/templates/registration/password\_reset\_email.html**, and give it the following contents:

```django
Someone asked for password reset for email {{ email }}. Follow the link below:
{{ protocol }}://{{ domain }}{% url 'password\_reset\_confirm' uidb64=uid token=token %}
```

#### Password reset confirm

This page is where you enter your new password after clicking the link in the password reset email. Create **/locallibrary/templates/registration/password\_reset\_confirm.html**, and give it the following contents:

```django
{% extends "base\_generic.html" %}

{% block content %}
    {% if validlink %}
        <p>Please enter (and confirm) your new password.</p>
        <form action="" method="post">
        {% csrf\_token %}
            <table>
                <tr>
                    <td>{{ form.new\_password1.errors }}
                        <label for="id\_new\_password1">New password:</label></td>
                    <td>{{ form.new\_password1 }}</td>
                </tr>
                <tr>
                    <td>{{ form.new\_password2.errors }}
                        <label for="id\_new\_password2">Confirm password:</label></td>
                    <td>{{ form.new\_password2 }}</td>
                </tr>
                <tr>
                    <td></td>
                    <td><input type="submit" value="Change my password"></td>
                </tr>
            </table>
        </form>
    {% else %}
        <h1>Password reset failed</h1>
        <p>The password reset link was invalid, possibly because it has already been used. Please request a new password reset.</p>
    {% endif %}
{% endblock %}
```

#### Password reset complete

This is the last password-reset template, which is displayed to notify you when the password reset has succeeded. Create **/locallibrary/templates/registration/password\_reset\_complete.html**, and give it the following contents:

```django
{% extends "base\_generic.html" %}

{% block content %}
  <h1>The password has been changed!</h1>
  <p><a href="{% url 'login' %}">log in again?</a></p>
{% endblock %}
```

### Testing the new authentication pages

Now that you've added the URL configuration and created all these templates, the authentication pages should now just work!

You can test the new authentication pages by attempting to log in to and then log out of your superuser account using these URLs:

* `http://127.0.0.1:8000/accounts/login/`
* `http://127.0.0.1:8000/accounts/logout/`

You'll be able to test the password reset functionality from the link in the login page. **Be aware that Django will only send reset emails to addresses (users) that are already stored in its database!**

**Note:** The password reset system requires that your website supports email, which is beyond the scope of this article, so this part **won't work yet**. To allow testing, put the following line at the end of your settings.py file. This logs any emails sent to the console (so you can copy the password reset link from the console).

```python
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
```

For more information, see Sending email (Django docs).

Testing against authenticated users
-----------------------------------

This section looks at what we can do to selectively control content the user sees based on whether they are logged in or not.

### Testing in templates

You can get information about the currently logged in user in templates with the `{{ user }}` template variable (this is added to the template context by default when you set up the project as we did in our skeleton).

Typically you will first test against the `{{ user.is_authenticated }}` template variable to determine whether the user is eligible to see specific content. To demonstrate this, next we'll update our sidebar to display a "Login" link if the user is logged out, and a "Logout" link if they are logged in.

Open the base template (**/locallibrary/catalog/templates/base\_generic.html**) and copy the following text into the `sidebar` block, immediately before the `endblock` template tag.

```django
  <ul class="sidebar-nav">

    …

   {% if user.is\_authenticated %}
     <li>User: {{ user.get\_username }}</li>
     <li><a href="{% url 'logout' %}?next={{ request.path }}">Logout</a></li>
   {% else %}
     <li><a href="{% url 'login' %}?next={{ request.path }}">Login</a></li>
   {% endif %}
  </ul>
```

As you can see, we use `if` / `else` / `endif` template tags to conditionally display text based on whether `{{ user.is_authenticated }}` is true. If the user is authenticated then we know that we have a valid user, so we call `{{ user.get_username }}` to display their name.

We create the login and logout link URLs using the `url` template tag and the names of the respective URL configurations. Note also how we have appended `?next={{ request.path }}` to the end of the URLs. What this does is add a URL parameter `next` containing the address (URL) of the *current* page, to the end of the linked URL. After the user has successfully logged in/out, the views will use this "`next`" value to redirect the user back to the page where they first clicked the login/logout link.

**Note:** Try it out! If you're on the home page and you click Login/Logout in the sidebar, then after the operation completes you should end up back on the same page.

### Testing in views

If you're using function-based views, the easiest way to restrict access to your functions is to apply the `login_required` decorator to your view function, as shown below. If the user is logged in then your view code will execute as normal. If the user is not logged in, this will redirect to the login URL defined in the project settings (`settings.LOGIN_URL`), passing the current absolute path as the `next` URL parameter. If the user succeeds in logging in then they will be returned back to this page, but this time authenticated.

```python
from django.contrib.auth.decorators import login_required

@login\_required
def my\_view(request):
    # …
```

**Note:** You can do the same sort of thing manually by testing on `request.user.is_authenticated`, but the decorator is much more convenient!

Similarly, the easiest way to restrict access to logged-in users in your class-based views is to derive from `LoginRequiredMixin`. You need to declare this mixin first in the superclass list, before the main view class.

```python
from django.contrib.auth.mixins import LoginRequiredMixin

class MyView(LoginRequiredMixin, View):
    # …
```

This has exactly the same redirect behavior as the `login_required` decorator. You can also specify an alternative location to redirect the user to if they are not authenticated (`login_url`), and a URL parameter name instead of "`next`" to insert the current absolute path (`redirect_field_name`).

```python
class MyView(LoginRequiredMixin, View):
    login_url = '/login/'
    redirect_field_name = 'redirect\_to'
```

For additional detail, check out the Django docs here.

Example — listing the current user's books
------------------------------------------

Now that we know how to restrict a page to a particular user, let's create a view of the books that the current user has borrowed.

Unfortunately, we don't yet have any way for users to borrow books! So before we can create the book list we'll first extend the `BookInstance` model to support the concept of borrowing and use the Django Admin application to loan a number of books to our test user.

### Models

First, we're going to have to make it possible for users to have a `BookInstance` on loan (we already have a `status` and a `due_back` date, but we don't yet have any association between this model and a particular user. We'll create one using a `ForeignKey` (one-to-many) field. We also need an easy mechanism to test whether a loaned book is overdue.

Open **catalog/models.py**, and import the `settings` from `django.conf` (add this just below the previous import line at the top of the file, so the settings are available to subsequent code that makes use of them):

```python
from django.conf import settings
```

 Next, add the `borrower` field to the `BookInstance` model, setting the user model for the key as the value of the setting `AUTH_USER_MODEL`.
 Since we have not overridden the setting with a custom user model this maps to the default `User` model from `django.contrib.auth.models`.

```python
borrower = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True)
```

**Note:** Importing the model in this way reduces the work required if you later discover that you need a custom user model.
 This tutorial uses the default model, so you could instead import the `User` model directly with the following lines:

```python
from django.contrib.auth.models import User
```

```python
borrower = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)
```

 While we're here, let's add a property that we can call from our templates to tell if a particular book instance is overdue.
 While we could calculate this in the template itself, using a property as shown below will be much more efficient.

Add this somewhere near the top of the file:

```python
from datetime import date
```

Now add the following property definition to the `BookInstance` class:

**Note:** The following code uses Python's `bool()` function, which evaluates an object or the resulting object of an expression, and returns `True` unless the result is "falsy", in which case it returns `False`.
 In Python an object is *falsy* (evaluates as `False`) if it is: empty (like `[]`, `()`, `{}`), `0`, `None` or if it is `False`.

```python
@property
def is\_overdue(self):
    """Determines if the book is overdue based on due date and current date."""
    return bool(self.due_back and date.today() > self.due_back)
```

**Note:** We first verify whether `due_back` is empty before making a comparison. An empty `due_back` field would cause Django to throw an error instead of showing the page: empty values are not comparable. This is not something we would want our users to experience!

Now that we've updated our models, we'll need to make fresh migrations on the project and then apply those migrations:

```bash
python3 manage.py makemigrations
python3 manage.py migrate
```

### Admin

 Now open **catalog/admin.py**, and add the `borrower` field to the `BookInstanceAdmin` class in both the `list_display` and the `fieldsets` as shown below.
 This will make the field visible in the Admin section, allowing us to assign a `User` to a `BookInstance` when needed.

```python
@admin.register(BookInstance)
class BookInstanceAdmin(admin.ModelAdmin):
    list_display = ('book', 'status', 'borrower', 'due\_back', 'id')
    list_filter = ('status', 'due\_back')

    fieldsets = (
        (None, {
            'fields': ('book', 'imprint', 'id')
        }),
        ('Availability', {
            'fields': ('status', 'due\_back', 'borrower')
        }),
    )
```

### Loan a few books

Now that it's possible to loan books to a specific user, go and loan out a number of `BookInstance` records. Set their `borrowed` field to your test user, make the `status` "On loan", and set due dates both in the future and the past.

**Note:** We won't spell the process out, as you already know how to use the Admin site!

### On loan view

Now we'll add a view for getting the list of all books that have been loaned to the current user. We'll use the same generic class-based list view we're familiar with, but this time we'll also import and derive from `LoginRequiredMixin`, so that only a logged in user can call this view. We will also choose to declare a `template_name`, rather than using the default, because we may end up having a few different lists of BookInstance records, with different views and templates.

Add the following to catalog/views.py:

```python
from django.contrib.auth.mixins import LoginRequiredMixin

class LoanedBooksByUserListView(LoginRequiredMixin,generic.ListView):
    """Generic class-based view listing books on loan to current user."""
    model = BookInstance
    template_name = 'catalog/bookinstance\_list\_borrowed\_user.html'
    paginate_by = 10

    def get\_queryset(self):
        return (
            BookInstance.objects.filter(borrower=self.request.user)
            .filter(status__exact='o')
            .order_by('due\_back')
        )
```

In order to restrict our query to just the `BookInstance` objects for the current user, we re-implement `get_queryset()` as shown above. Note that "o" is the stored code for "on loan" and we order by the `due_back` date so that the oldest items are displayed first.

### URL conf for on loan books

Now open **/catalog/urls.py** and add a `path()` pointing to the above view (you can just copy the text below to the end of the file).

```python
urlpatterns += [
    path('mybooks/', views.LoanedBooksByUserListView.as_view(), name='my-borrowed'),
]
```

### Template for on-loan books

Now, all we need to do for this page is add a template. First, create the template file **/catalog/templates/catalog/bookinstance\_list\_borrowed\_user.html** and give it the following contents:

```django
{% extends "base\_generic.html" %}

{% block content %}
    <h1>Borrowed books</h1>

    {% if bookinstance\_list %}
    <ul>

      {% for bookinst in bookinstance\_list %}
      <li class="{% if bookinst.is\_overdue %}text-danger{% endif %}">
        <a href="{% url 'book-detail' bookinst.book.pk %}">{{ bookinst.book.title }}</a> ({{ bookinst.due\_back }})
      </li>
      {% endfor %}
    </ul>

    {% else %}
      <p>There are no books borrowed.</p>
    {% endif %}
{% endblock %}
```

 This template is very similar to those we've created previously for the `Book` and `Author` objects.
 The only "new" thing here is that we check the method we added in the model `(bookinst.is_overdue`) and use it to change the color of overdue items.

When the development server is running, you should now be able to view the list for a logged in user in your browser at `http://127.0.0.1:8000/catalog/mybooks/`. Try this out with your user logged in and logged out (in the second case, you should be redirected to the login page).

### Add the list to the sidebar

The very last step is to add a link for this new page into the sidebar. We'll put this in the same section where we display other information for the logged in user.

Open the base template (**/locallibrary/catalog/templates/base\_generic.html**) and add the "My Borrowed" line to the sidebar in the position shown below.

```django
 <ul class="sidebar-nav">
   {% if user.is\_authenticated %}
   <li>User: {{ user.get\_username }}</li>

   <li><a href="{% url 'my-borrowed' %}">My Borrowed</a></li>

   <li><a href="{% url 'logout' %}?next={{ request.path }}">Logout</a></li>
   {% else %}
   <li><a href="{% url 'login' %}?next={{ request.path }}">Login</a></li>
   {% endif %}
 </ul>
```

### What does it look like?

When any user is logged in, they'll see the *My Borrowed* link in the sidebar, and the list of books displayed as below (the first book has no due date, which is a bug we hope to fix in a later tutorial!).

![Library - borrowed books by user](/en-US/docs/Learn/Server-side/Django/Authentication/library_borrowed_by_user.png)

Permissions
-----------

Permissions are associated with models and define the operations that can be performed on a model instance by a user who has the permission. By default, Django automatically gives *add*, *change*, and *delete* permissions to all models, which allow users with the permissions to perform the associated actions via the admin site. You can define your own permissions to models and grant them to specific users. You can also change the permissions associated with different instances of the same model.

Testing on permissions in views and templates is then very similar to testing on the authentication status (and in fact, testing for a permission also tests for authentication).

### Models

 Defining permissions is done on the model "`class Meta`" section, using the `permissions` field.
 You can specify as many permissions as you need in a tuple, each permission itself being defined in a nested tuple containing the permission name and permission display value.
 For example, we might define a permission to allow a user to mark that a book has been returned as shown:

```python
class BookInstance(models.Model):
    # …
    class Meta:
        # …
        permissions = (("can\_mark\_returned", "Set book as returned"),)
```

We could then assign the permission to a "Librarian" group in the Admin site.

Open the **catalog/models.py**, and add the permission as shown above. You will need to re-run your migrations (call `python3 manage.py makemigrations` and `python3 manage.py migrate`) to update the database appropriately.

### Templates

The current user's permissions are stored in a template variable called `{{ perms }}`. You can check whether the current user has a particular permission using the specific variable name within the associated Django "app" — e.g. `{{ perms.catalog.can_mark_returned }}` will be `True` if the user has this permission, and `False` otherwise. We typically test for the permission using the template `{% if %}` tag as shown:

```django
{% if perms.catalog.can\_mark\_returned %}
    <!-- We can mark a BookInstance as returned. -->
    <!-- Perhaps add code to link to a "book return" view here. -->
{% endif %}
```

### Views

Permissions can be tested in function view using the `permission_required` decorator or in a class-based view using the `PermissionRequiredMixin`. The pattern are the same as for login authentication, though of course, you might reasonably have to add multiple permissions.

Function view decorator:

```python
from django.contrib.auth.decorators import permission_required

@permission\_required('catalog.can\_mark\_returned')
@permission\_required('catalog.can\_edit')
def my\_view(request):
    # …
```

A permission-required mixin for class-based views.

```python
from django.contrib.auth.mixins import PermissionRequiredMixin

class MyView(PermissionRequiredMixin, View):
    permission_required = 'catalog.can\_mark\_returned'
    # Or multiple permissions
    permission_required = ('catalog.can\_mark\_returned', 'catalog.change\_book')
    # Note that 'catalog.change\_book' is permission
    # Is created automatically for the book model, along with add\_book, and delete\_book
```

**Note:** There is a small default difference in the behavior above. By **default** for a logged-in user with a permission violation:

* `@permission_required` redirects to login screen (HTTP Status 302).
* `PermissionRequiredMixin` returns 403 (HTTP Status Forbidden).

Normally you will want the `PermissionRequiredMixin` behavior: return 403 if a user is logged in but does not have the correct permission. To do this for a function view use `@login_required` and `@permission_required` with `raise_exception=True` as shown:

```python
from django.contrib.auth.decorators import login_required, permission_required

@login\_required
@permission\_required('catalog.can\_mark\_returned', raise_exception=True)
def my\_view(request):
    # …
```

### Example

We won't update the *LocalLibrary* here; perhaps in the next tutorial!

Challenge yourself
------------------

 Earlier in this article, we showed you how to create a page for the current user, listing the books that they have borrowed.
 The challenge now is to create a similar page that is only visible for librarians, that displays *all* books that have been borrowed, and which includes the name of each borrower.

You should be able to follow the same pattern as for the other view. The main difference is that you'll need to restrict the view to only librarians. You could do this based on whether the user is a staff member (function decorator: `staff_member_required`, template variable: `user.is_staff`) but we recommend that you instead use the `can_mark_returned` permission and `PermissionRequiredMixin`, as described in the previous section.

**Warning:** Remember not to use your superuser for permissions based testing (permission checks always return true for superusers, even if a permission has not yet been defined!). Instead, create a librarian user, and add the required capability.

When you are finished, your page should look something like the screenshot below.

![All borrowed books, restricted to librarian](/en-US/docs/Learn/Server-side/Django/Authentication/library_borrowed_all.png)

Summary
-------

Excellent work — you've now created a website where library members can log in and view their own content, and where librarians (with the correct permission) can view all loaned books and their borrowers. At the moment we're still just viewing content, but the same principles and techniques are used when you want to start modifying and adding data.

In our next article, we'll look at how you can use Django forms to collect user input, and then start modifying some of our stored data.

See also
--------

* User authentication in Django (Django docs)
* Using the (default) Django authentication system (Django docs)
* Introduction to class-based views > Decorating class-based views (Django docs)
* Previous
* Overview: Django
* Next


Django Tutorial Part 2: Creating a skeleton website - Learn web development

Django Tutorial Part 2: Creating a skeleton website
===================================================

* Previous
* Overview: Django
* Next

This second article in our Django Tutorial shows how you can create a "skeleton" website project as a basis, which you can then populate with site-specific settings, paths, models, views, and templates.

|  |  |
| --- | --- |
| Prerequisites: | Set up a Django development environment.
 Review the Django Tutorial.
  |
| Objective: | To be able to use Django's tools to start your own new website projects. |

Overview
--------

This article shows how you can create a "skeleton" website, which you can then populate with site-specific settings, paths, models, views, and templates (we discuss these in later articles).

To get started:

1. Use the `django-admin` tool to generate a project folder, the basic file templates, and **manage.py**, which serves as your project management script.
2. Use **manage.py** to create one or more *applications*.

**Note:** A website may consist of one or more sections. For example, main site, blog, wiki, downloads area, etc. Django encourages you to develop these components as separate *applications*, which could then be re-used in different projects if desired.
3. Register the new applications to include them in the project.
4. Hook up the **url/path** mapper for each application.

 For the Local Library website, the website and project folders are named *locallibrary*, and includes one application named *catalog*.
 The top-level folder structure will therefore be as follows:

```bash
locallibrary/         # Website folder
    manage.py         # Script to run Django tools for this project (created using django-admin)
    locallibrary/     # Website/project folder (created using django-admin)
    catalog/          # Application folder (created using manage.py)
```

 The following sections discuss the process steps in detail, and show how you can test your changes.
 At the end of this article, we discuss other site-wide configuration you might also do at this stage.

Creating the project
--------------------

To create the project:

1. Open a command shell (or a terminal window), and make sure you are in your virtual environment.
2. Navigate to where you want to store your Django apps (make it somewhere easy to find like inside your *Documents* folder), and create a folder for your new website (in this case: *django\_projects*). Then change into your newly-created directory:

```bash
mkdir django_projects
cd django_projects
```

3. Create the new project using the `django-admin startproject` command as shown, and then change into the project folder:

```bash
django-admin startproject locallibrary
cd locallibrary
```

The `django-admin` tool creates a folder/file structure as follows:

```bash
locallibrary/
    manage.py
    locallibrary/
        __init__.py
        settings.py
        urls.py
        wsgi.py
        asgi.py
```

Our current working directory should look something like this:

```python
../django_projects/locallibrary/
```

The *locallibrary* project sub-folder is the entry point for the website:

* **\_\_init\_\_.py** is an empty file that instructs Python to treat this directory as a Python package.
* **settings.py** contains all the website settings, including registering any applications we create, the location of our static files, database configuration details, etc.
* **urls.py** defines the site URL-to-view mappings. While this could contain *all* the URL mapping code, it is more common to delegate some of the mappings to particular applications, as you'll see later.
* **wsgi.py** is used to help your Django application communicate with the web server. You can treat this as boilerplate.
* **asgi.py** is a standard for Python asynchronous web apps and servers to communicate with each other. Asynchronous Server Gateway Interface (ASGI) is the asynchronous successor to Web Server Gateway Interface (WSGI). ASGI provides a standard for both asynchronous and synchronous Python apps, whereas WSGI provided a standard for synchronous apps only. ASGI is backward-compatible with WSGI and supports multiple servers and application frameworks.

The **manage.py** script is used to create applications, work with databases, and start the development web server.

Creating the catalog application
--------------------------------

Next, run the following command to create the *catalog* application that will live inside our *locallibrary* project. Make sure to run this command from the same folder as your project's **manage.py**:

```bash
# Linux/macOS
python3 manage.py startapp catalog

# Windows
py manage.py startapp catalog
```

**Note:** The rest of the tutorial uses the Linux/macOS syntax.
 If you're working on Windows, wherever you see a command starting with `python3` you should instead use `py` (or `py -3`).

 The tool creates a new folder and populates it with files for the different parts of the application (shown in the following example).
 Most of the files are named after their purpose (e.g. views should be stored in **views.py**, models in **models.py**, tests in **tests.py**, administration site configuration in **admin.py**, application registration in **apps.py**) and contain some minimal boilerplate code for working with the associated objects.

The updated project directory should now look like this:

```bash
locallibrary/
    manage.py
    locallibrary/
    catalog/
        admin.py
        apps.py
        models.py
        tests.py
        views.py
        __init__.py
        migrations/
```

In addition we now have:

* A *migrations* folder, used to store "migrations" — files that allow you to automatically update your database as you modify your models.
* **\_\_init\_\_.py** — an empty file created here so that Django/Python will recognize the folder as a Python Package and allow you to use its objects within other parts of the project.

**Note:** Have you noticed what is missing from the files list above? While there is a place for your views and models, there is nowhere for you to put your URL mappings, templates, and static files. We'll show you how to create them further along (these aren't needed in every website but they are needed in this example).

Registering the catalog application
-----------------------------------

Now that the application has been created, we have to register it with the project so that it will be included when any tools are run (like adding models to the database for example). Applications are registered by adding them to the `INSTALLED_APPS` list in the project settings.

Open the project settings file, **django\_projects/locallibrary/locallibrary/settings.py**, and find the definition for the `INSTALLED_APPS` list. Then add a new line at the end of the list, as shown below:

```bash
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    # Add our new application
    'catalog.apps.CatalogConfig', #This object was created for us in /catalog/apps.py
]
```

The new line specifies the application configuration object (`CatalogConfig`) that was generated for you in **/locallibrary/catalog/apps.py** when you created the application.

**Note:** You'll notice that there are already a lot of other `INSTALLED_APPS` (and `MIDDLEWARE`, further down in the settings file). These enable support for the Django administration site and the functionality it uses (including sessions, authentication, etc.).

Specifying the database
-----------------------

This is also the point where you would normally specify the database to be used for the project. It makes sense to use the same database for development and production where possible, in order to avoid minor differences in behavior. You can find out about the different options in Databases (Django docs).

We'll use the default SQLite database for this example, because we don't expect to require a lot of concurrent access on a demonstration database, and it requires no additional work to set up! You can see how this database is configured in **settings.py**:

```python
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    }
}
```

Because we are using SQLite, we don't need to do any further setup here. Let's move on!

Other project settings
----------------------

The **settings.py** file is also used for configuring a number of other settings, but at this point, you probably only want to change the TIME\_ZONE — this should be made equal to a string from the standard List of tz database time zones (the TZ column in the table contains the values you want). Change your `TIME_ZONE` value to one of these strings appropriate for your time zone, for example:

```python
TIME_ZONE = 'Europe/London'
```

There are two other settings you won't change now, but that you should be aware of:

* `SECRET_KEY`. This is a secret key that is used as part of Django's website security strategy. If you're not protecting this code in development, you'll need to use a different code (perhaps read from an environment variable or file) when putting it into production.
* `DEBUG`. This enables debugging logs to be displayed on error, rather than HTTP status code responses. This should be set to `False` in production as debug information is useful for attackers, but for now we can keep it set to `True`.
Hooking up the URL mapper
-------------------------

The website is created with a URL mapper file (**urls.py**) in the project folder. While you can use this file to manage all your URL mappings, it is more usual to defer mappings to the associated application.

Open **locallibrary/locallibrary/urls.py** and note the instructional text which explains some of the ways to use the URL mapper.

```python
"""
URL configuration for locallibrary project.

The `urlpatterns` list routes URLs to views. For more information please see:
 https://docs.djangoproject.com/en/4.2/topics/http/urls/
Examples:
Function views
 1. Add an import: from my\_app import views
 2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
 1. Add an import: from other\_app.views import Home
 2. Add a URL to urlpatterns: path('', Home.as\_view(), name='home')
Including another URLconf
 1. Import the include() function: from django.urls import include, path
 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path

urlpatterns = [
    path('admin/', admin.site.urls),
]
```

The URL mappings are managed through the `urlpatterns` variable, which is a Python *list* of `path()` functions. Each `path()` function either associates a URL pattern to a *specific view*, which will be displayed when the pattern is matched, or with another list of URL pattern testing code (in this second case, the pattern becomes the "base URL" for patterns defined in the target module). The `urlpatterns` list initially defines a single function that maps all URLs with the pattern *admin/* to the module `admin.site.urls`, which contains the Administration application's own URL mapping definitions.

**Note:** The route in `path()` is a string defining a URL pattern to match. This string might include a named variable (in angle brackets), e.g. `'catalog/<id>/'`. This pattern will match a URL like **catalog/*any\_chars*/** and pass *`any_chars`* to the view as a string with the parameter name `id`. We discuss path methods and route patterns further in later topics.

To add a new list item to the `urlpatterns` list, add the following lines to the bottom of the file. This new item includes a `path()` that forwards requests with the pattern `catalog/` to the module `catalog.urls` (the file with the relative URL **catalog/urls.py**).

```python
# Use include() to add paths from the catalog application
from django.urls import include

urlpatterns += [
    path('catalog/', include('catalog.urls')),
]
```

**Note:** Note that we included the import line (`from django.urls import include`) with the code that uses it (so it is easy to see what we've added), but it is common to include all your import lines at the top of a Python file.

Now let's redirect the root URL of our site (i.e. `127.0.0.1:8000`) to the URL `127.0.0.1:8000/catalog/`. This is the only app we'll be using in this project. To do this, we'll use a special view function, `RedirectView`, which takes the new relative URL to redirect to (`/catalog/`) as its first argument when the URL pattern specified in the `path()` function is matched (the root URL, in this case).

Add the following lines to the bottom of the file:

```python
#Add URL maps to redirect the base URL to our application
from django.views.generic import RedirectView
urlpatterns += [
    path('', RedirectView.as_view(url='catalog/', permanent=True)),
]
```

Leave the first parameter of the path function empty to imply '/'. If you write the first parameter as '/' Django will give you the following warning when you start the development server:

```python
System check identified some issues:

WARNINGS:
?: (urls.W002) Your URL pattern '/' has a route beginning with a '/'.
Remove this slash as it is unnecessary.
If this pattern is targeted in an include(), ensure the include() pattern has a trailing '/'.
```

Django does not serve static files like CSS, JavaScript, and images by default, but it can be useful for the development web server to do so while you're creating your site. As a final addition to this URL mapper, you can enable the serving of static files during development by appending the following lines.

Add the following final block to the bottom of the file now:

```python
# Use static() to add URL mapping to serve static files during development (only)
from django.conf import settings
from django.conf.urls.static import static

urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
```

**Note:** There are a number of ways to extend the `urlpatterns` list (previously, we just appended a new list item using the `+=` operator to clearly separate the old and new code). We could have instead just included this new pattern-map in the original list definition:

```python
urlpatterns = [
    path('admin/', admin.site.urls),
    path('catalog/', include('catalog.urls')),
    path('', RedirectView.as_view(url='catalog/')),
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
```

As a final step, create a file inside your catalog folder called **urls.py**, and add the following text to define the (empty) imported `urlpatterns`. This is where we'll add our patterns as we build the application.

```python
from django.urls import path
from . import views

urlpatterns = [

]
```

Testing the website framework
-----------------------------

At this point we have a complete skeleton project. The website doesn't actually *do* anything yet, but it's worth running it to make sure that none of our changes have broken anything.

Before we do that, we should first run a *database migration*. This updates our database (to include any models in our installed applications) and removes some build warnings.

### Running database migrations

Django uses an Object-Relational-Mapper (ORM) to map model definitions in the Django code to the data structure used by the underlying database. As we change our model definitions, Django tracks the changes and can create database migration scripts (in **/locallibrary/catalog/migrations/**) to automatically migrate the underlying data structure in the database to match the model.

When we created the website, Django automatically added a number of models for use by the admin section of the site (which we'll look at later). Run the following commands to define tables for those models in the database (make sure you are in the directory that contains **manage.py**):

```bash
python3 manage.py makemigrations
python3 manage.py migrate
```

**Warning:** You'll need to run these commands every time your models change in a way that will affect the structure of the data that needs to be stored (including both addition and removal of whole models and individual fields).

The `makemigrations` command *creates* (but does not apply) the migrations for all applications installed in your project. You can specify the application name as well to just run a migration for a single app. This gives you a chance to check out the code for these migrations before they are applied. If you're a Django expert, you may choose to tweak them slightly!

The `migrate` command is what applies the migrations to your database. Django tracks which ones have been added to the current database.

**Note:** See Migrations (Django docs) for additional information about the lesser-used migration commands.

### Running the website

During development, you can serve the website first using the *development web server*, and then viewing it on your local web browser.

**Note:** The development web server is not robust or performant enough for production use, but it is a very easy way to get your Django website up and running during development to give it a convenient quick test. By default it will serve the site to your local computer (`http://127.0.0.1:8000/)`, but you can also specify other computers on your network to serve to. For more information see django-admin and manage.py: runserver (Django docs).

Run the *development web server* by calling the `runserver` command (in the same directory as **manage.py**):

```bash
python3 manage.py runserver
```

Once the server is running, you can view the site by navigating to `http://127.0.0.1:8000/` in your local web browser. You should see a site error page that looks like this:

![Django Debug page (Django 4.2)](/en-US/docs/Learn/Server-side/Django/skeleton_website/django_404_debug_page.png)

Don't worry! This error page is expected because we don't have any pages/urls defined in the `catalog.urls` module (which we're redirected to when we get a URL to the root of the site).

**Note:** The example page demonstrates a great Django feature — automated debug logging. Whenever a page cannot be found, Django displays an error screen with useful information or any error raised by the code. In this case, we can see that the URL we've supplied doesn't match any of our URL patterns (as listed). Logging is turned off in production (which is when we put the site live on the Web), in which case a less informative but more user-friendly page will be served.

At this point, we know that Django is working!

**Note:** You should re-run migrations and re-test the site whenever you make significant changes. It doesn't take very long!

Challenge yourself
------------------

The **catalog/** directory contains files for the views, models, and other parts of the application. Open these files and inspect the boilerplate.

As you saw previously, a URL-mapping for the Admin site has already been added in the project's **urls.py**. Navigate to the admin area in your browser and see what happens (you can infer the correct URL from the mapping).

Summary
-------

You have now created a complete skeleton website project, which you can go on to populate with URLs, models, views, and templates.

Now that the skeleton for the Local Library website is complete and running, it's time to start writing the code that makes this website do what it is supposed to do.

See also
--------

* Writing your first Django app - part 1 (Django docs)
* Applications (Django Docs).
 Contains information on configuring applications.
* Previous
* Overview: Django
* Next


CSS property compatibility table for form controls - Learn web development

CSS property compatibility table for form controls
==================================================

The following compatibility tables try to summarize the state of CSS support for HTML forms. Due to the complexity of CSS and HTML forms, these tables can't be considered a perfect reference. However, they will give you good insight into what can and can't be done, which will help you learn how to do things.

How to read the tables
----------------------

### Values

For each property, there are four possible values:

✅ Yes

There's reasonably consistent support for the property across browsers. You may still face strange side effects in certain edge cases.

⚠ Partial

While the property works, you may frequently face strange side effects or inconsistencies. You should probably avoid these properties unless you master those side effects first.

❌ No

The property doesn't work or is so inconsistent that it's not reliable.

n.a.

The property has no meaning for this type of widget.

### Rendering

For each property there are two possible renderings:

N (Normal)

Indicates that the property is applied as it is

T (Tweaked)

Indicates that the property is applied with the extra rule below:

```css
\* {
  /\* Turn off the native look and feel \*/
  appearance: none;
}
```

Compatibility tables
--------------------

Altering the appearance of form controls with CSS, such as with `border`, `background`, `border-radius`, and `height` can partially or fully turn off the native look & feel of widgets on some browsers. Be careful when you use them.

### Text fields

See the `text`, `search`, and `password` input types.

| Property | N | T | Note |
| --- | --- | --- | --- |
| *CSS box model* |
| --- |
| `width` | ✅ Yes | ✅ Yes |  |
| `height` | ⚠ Partial[1] | ✅ Yes | 1. WebKit browsers (mostly on Mac OSX and iOS) use the native look
 & feel for the search fields. Therefore, it's required to use
 `appearance:none` to be able to apply this
 property to search fields.
 |
| `border` | ⚠ Partial[1] | ✅ Yes | 1. WebKit browsers (mostly on Mac OSX and iOS) use the native look
 & feel for the search fields. Therefore, it's required to use
 `appearance:none` to be able to apply this
 property to search fields.
 |
| `margin` | ✅ Yes | ✅ Yes |  |
| `padding` | ⚠ Partial[1] | ✅ Yes | 1. WebKit browsers (mostly on Mac OSX and iOS) use the native look
 & feel for the search fields. Therefore, it's required to use
 `appearance:none` to be able to apply this
 property to search fields.
 |
| *Text and font* |
| --- |
| `color`[1] | ✅ Yes | ✅ Yes | 1. If the `border-color` property is not set,
 some WebKit based browsers will apply the
 `color` property to the border as well as the
 font on ``<textarea>``s.
 |
| `font` | ✅ Yes | ✅ Yes | See the note about `line-height` |
| `letter-spacing` | ✅ Yes | ✅ Yes |  |
| `text-align` | ✅ Yes | ✅ Yes |  |
| `text-decoration` | ⚠ Partial | ⚠ Partial | See the note about Opera |
| `text-indent` | ⚠ Partial[1] | ⚠ Partial[1] | 1. IE9 supports this property only on
 ``<textarea>``s, whereas Opera
 only supports it on single line text fields.
 |
| `text-overflow` | ⚠ Partial | ⚠ Partial |  |
| `text-shadow` | ⚠ Partial | ⚠ Partial |  |
| `text-transform` | ✅ Yes | ✅ Yes |  |
| *Border and background* |
| --- |
| `background` | ⚠ Partial[1] | ✅ Yes | 1. WebKit browsers (mostly on Mac OSX and iOS) use the native look
 & feel for the search fields. Therefore, it's required to use
 `-webkit-appearance:none` to be able to apply this
 property to search fields.
 |
| `border-radius` | ⚠ Partial[1] | ✅ Yes | 1. WebKit browsers (mostly on Mac OSX and iOS) use the native look
 & feel for the search fields. Therefore, it's required to use
 `-webkit-appearance:none` to be able to apply this
 property to search fields.
 |
| `box-shadow` | ❌ No | ⚠ Partial[1] | 1. IE9 does not support this property.
 |

### Buttons

See the `button`, `submit`, and `reset` input types and the ``<button>`` element.

| Property | N | T | Note |
| --- | --- | --- | --- |
| *CSS box model* |
| --- |
| `width` | ✅ Yes | ✅ Yes |  |
| `height` | ⚠ Partial[1] | ✅ Yes | 1. This property is not applied on WebKit based browsers on Mac OSX or
 iOS.
 |
| `border` | ⚠ Partial | ✅ Yes |  |
| `margin` | ✅ Yes | ✅ Yes |  |
| `padding` | ⚠ Partial[1] | ✅ Yes | 1. This property is not applied on WebKit based browsers on Mac OSX or
 iOS.
 |
| *Text and font* |
| --- |
| `color` | ✅ Yes | ✅ Yes |  |
| `font` | ✅ Yes | ✅ Yes | See the note about `line-height`. |
| `letter-spacing` | ✅ Yes | ✅ Yes |  |
| `text-align` | ❌ No | ❌ No |  |
| `text-decoration` | ⚠ Partial | ✅ Yes |  |
| `text-indent` | ✅ Yes | ✅ Yes |  |
| `text-overflow` | ❌ No | ❌ No |  |
| `text-shadow` | ⚠ Partial | ⚠ Partial |  |
| `text-transform` | ✅ Yes | ✅ Yes |  |
| *Border and background* |
| --- |
| `background` | ✅ Yes | ✅ Yes |  |
| `border-radius` | ✅ Yes[1] | ✅ Yes[1] | 1. On Opera the `border-radius` property is
 applied only if an explicit border is set.
 |
| `box-shadow` | ❌ No | ⚠ Partial[1] | 1. IE9 does not support this property.
 |

### Number

See the `number` input type. There is no standard way to change the style of spinners used to change the value of the field, with the spinners on Safari being outside the field.

| Property | N | T | Note |
| --- | --- | --- | --- |
| *CSS box model* |
| --- |
| `width` | ✅ Yes | ✅ Yes |  |
| `height` | ⚠ Partial[1] | ⚠ Partial[1] | 1. On Opera, the spinners are zoomed in, which can hide the content of
 the field.
 |
| `border` | ✅ Yes | ✅ Yes |  |
| `margin` | ✅ Yes | ✅ Yes |  |
| `padding` | ⚠ Partial[1] | ⚠ Partial[1] | 1. On Opera, the spinners are zoomed in, which can hide the content of
 the field.
 |
| *Text and font* |
| --- |
| `color` | ✅ Yes | ✅ Yes |  |
| `font` | ✅ Yes | ✅ Yes | See the note about `line-height`. |
| `letter-spacing` | ✅ Yes | ✅ Yes |  |
| `text-align` | ✅ Yes | ✅ Yes |  |
| `text-decoration` | ⚠ Partial | ⚠ Partial |  |
| `text-indent` | ✅ Yes | ✅ Yes |  |
| `text-overflow` | ❌ No | ❌ No |  |
| `text-shadow` | ⚠ Partial | ⚠ Partial |  |
| `text-transform` | N.A. | N.A. |  |
| *Border and background* |
| --- |
| `background` | ❌ No | ❌ No |
 Supported but there is too much inconsistency between browsers to be
 reliable.
  |
| `border-radius` | ❌ No | ❌ No |
| `box-shadow` | ❌ No | ❌ No |

### Check boxes and radio buttons

See the `checkbox` and `radio` input types.

| Property | N | T | Note |
| --- | --- | --- | --- |
| *CSS box model* |
| --- |
| `width` | ❌ No[1] | ❌ No[1] | 1. Some browsers add extra margins and others stretch the widget.
 |
| `height` | ❌ No[1] | ❌ No[1] | 1. Some browsers add extra margins and others stretch the widget.
 |
| `border` | ❌ No | ❌ No |  |
| `margin` | ✅ Yes | ✅ Yes |  |
| `padding` | ❌ No | ❌ No |  |
| *Text and font* |
| --- |
| `color` | N.A. | N.A. |  |
| `font` | N.A. | N.A. |  |
| `letter-spacing` | N.A. | N.A. |  |
| `text-align` | N.A. | N.A. |  |
| `text-decoration` | N.A. | N.A. |  |
| `text-indent` | N.A. | N.A. |  |
| `text-overflow` | N.A. | N.A. |  |
| `text-shadow` | N.A. | N.A. |  |
| `text-transform` | N.A. | N.A. |  |
| *Border and background* |
| --- |
| `background` | ❌ No | ❌ No |  |
| `border-radius` | ❌ No | ❌ No |  |
| `box-shadow` | ❌ No | ❌ No |  |

### Select boxes (single line)

See the ``<select>``, ``<optgroup>`` and ``<option>`` elements.

| Property | N | T | Note |
| --- | --- | --- | --- |
| *CSS box model* |
| --- |
| `width` | ⚠ Partial[1] | ⚠ Partial[1] | 1. This property is okay on the
 ``<select>`` element, but it cannot
 be the case on the ``<option>`` or
 ``<optgroup>`` elements.
 |
| `height` | ❌ No | ✅ Yes |  |
| `border` | ⚠ Partial | ✅ Yes |  |
| `margin` | ✅ Yes | ✅ Yes |  |
| `padding` | ❌ No[1] | ⚠ Partial[2] | 1. The property is applied, but in an inconsistent way between browsers
 on Mac OSX.
2. The property is well applied on the
 ``<select>`` element, but is
 inconsistently handled on
 ``<option>`` and
 ``<optgroup>`` elements.
 |
| *Text and font* |
| --- |
| `color` | ⚠ Partial[1] | ⚠ Partial[1] | 1. On Mac OSX, WebKit based browsers do not support this property on
 native widgets and they, along with Opera, do not support it at all
 on ``<option>`` and
 ``<optgroup>`` elements.
 |
| `font` | ⚠ Partial[1] | ⚠ Partial[1] | 1. On Mac OSX, WebKit based browsers do not support this property on
 native widgets and they, along with Opera, do not support it at all
 on ``<option>`` and
 ``<optgroup>`` elements.
 |
| `letter-spacing` | ⚠ Partial[1] | ⚠ Partial[1] | 1. IE9 does not support this property on
 ``<select>``,
 ``<option>``, and
 ``<optgroup>`` elements; WebKit
 based browsers on Mac OSX do not support this property on
 ``<option>`` and
 ``<optgroup>`` elements.
 |
| `text-decoration` | ⚠ Partial[1] | ⚠ Partial[1] | 1. Only Firefox provides full support for this property. Other browsers only support it on
 the ``<select>`` element.
 |
| `text-indent` | ⚠ Partial[1] | ⚠ Partial[1] | 1. Most of the browsers only support this property on the
 ``<select>`` element.
 |
| `text-overflow` | ❌ No | ❌ No |  |
| `text-shadow` | ⚠ Partial[1] | ⚠ Partial[1] | 1. Most of the browsers only support this property on the
 ``<select>`` element.
 |
| `text-transform` | ⚠ Partial[1] | ⚠ Partial[1] | 1. Most of the browsers only support this property on the
 ``<select>`` element.
 |
| *Border and background* |
| --- |
| `background` | ⚠ Partial[1] | ⚠ Partial[1] | 1. Most of the browsers only support this property on the
 ``<select>`` element.
 |
| `border-radius` | ⚠ Partial[1] | ⚠ Partial[1] |
| `box-shadow` | ❌ No | ⚠ Partial[1] |

Note Firefox does not provide any way to change the down arrow on the ``<select>`` element.

### Select boxes (multiline)

See the ``<select>``, ``<optgroup>`` and ``<option>`` elements and the `size` attribute.

| Property | N | T | Note |
| --- | --- | --- | --- |
| *CSS box model* |
| --- |
| `width` | ✅ Yes | ✅ Yes |  |
| `height` | ✅ Yes | ✅ Yes |  |
| `border` | ✅ Yes | ✅ Yes |  |
| `margin` | ✅ Yes | ✅ Yes |  |
| `padding` | ⚠ Partial[1] | ⚠ Partial[1] | 1. Opera does not support `padding-top` and
 `padding-bottom` on the
 ``<select>`` element.
 |
| *Text and font* |
| --- |
| `color` | ✅ Yes | ✅ Yes |  |
| `font` | ✅ Yes | ✅ Yes | See the note about `line-height`. |
| `letter-spacing` | ⚠ Partial[1] | ⚠ Partial[1] | 1. IE9 does not support this property on
 ``<select>``,
 ``<option>``, and
 ``<optgroup>`` elements; WebKit
 based browsers on Mac OSX do not support this property on
 ``<option>`` and
 ``<optgroup>`` elements.
 |
| `text-align` | ❌ No[1] | ❌ No[1] | 1. WebKit based browser on Mac OSX do not support
 this property on this widget.
 |
| `text-decoration` | ❌ No[1] | ❌ No[1] | 1. Only supported by Firefox and IE9+.
 |
| `text-indent` | ❌ No | ❌ No |  |
| `text-overflow` | ❌ No | ❌ No |  |
| `text-shadow` | ❌ No | ❌ No |  |
| `text-transform` | ⚠ Partial[1] | ⚠ Partial[1] | 1. Most of the browsers only support this property on the
 ``<select>`` element.
 |
| *Border and background* |
| --- |
| `background` | ✅ Yes | ✅ Yes |  |
| `border-radius` | ✅ Yes[1] | ✅ Yes[1] | 1. On Opera the `border-radius` property is
 applied only if an explicit border is set.
 |
| `box-shadow` | ❌ No | ⚠ Partial[1] | 1. IE9 does not support this property.
 |

### Datalist

See the ``<datalist>`` and ``<input>`` elements and the `list` attribute.

| Property | N | T | Note |
| --- | --- | --- | --- |
| *CSS box model* |
| --- |
| `width` | ❌ No | ❌ No |  |
| `height` | ❌ No | ❌ No |  |
| `border` | ❌ No | ❌ No |  |
| `margin` | ❌ No | ❌ No |  |
| `padding` | ❌ No | ❌ No |  |
| *Text and font* |
| --- |
| `color` | ❌ No | ❌ No |  |
| `font` | ❌ No | ❌ No |  |
| `letter-spacing` | ❌ No | ❌ No |  |
| `text-align` | ❌ No | ❌ No |  |
| `text-decoration` | ❌ No | ❌ No |  |
| `text-indent` | ❌ No | ❌ No |  |
| `text-overflow` | ❌ No | ❌ No |  |
| `text-shadow` | ❌ No | ❌ No |  |
| `text-transform` | ❌ No | ❌ No |  |
| *Border and background* |
| --- |
| `background` | ❌ No | ❌ No |  |
| `border-radius` | ❌ No | ❌ No |  |
| `box-shadow` | ❌ No | ❌ No |  |

### File picker

See the `file` input type.

| Property | N | T | Note |
| --- | --- | --- | --- |
| *CSS box model* |
| --- |
| `width` | ❌ No | ❌ No |  |
| `height` | ❌ No | ❌ No |  |
| `border` | ❌ No | ❌ No |  |
| `margin` | ✅ Yes | ✅ Yes |  |
| `padding` | ❌ No | ❌ No |  |
| *Text and font* |
| --- |
| `color` | ✅ Yes | ✅ Yes |  |
| `font` | ❌ No[1] | ❌ No[1] | 1. Supported, but there is too much inconsistency between browsers to
 be reliable.
 |
| `letter-spacing` | ⚠ Partial[1] | ⚠ Partial[1] | 1. Many browsers apply this property to the select button.
 |
| `text-align` | ❌ No | ❌ No |  |
| `text-decoration` | ❌ No | ❌ No |  |
| `text-indent` | ⚠ Partial[1] | ⚠ Partial[1] | 1. It acts more or less like an extra left margin outside the widget.
 |
| `text-overflow` | ❌ No | ❌ No |  |
| `text-shadow` | ❌ No | ❌ No |  |
| `text-transform` | ❌ No | ❌ No |  |
| *Border and background* |
| --- |
| `background` | ❌ No[1] | ❌ No[1] | 1. Supported, but there is too much inconsistency between browsers to
 be reliable.
 |
| `border-radius` | ❌ No | ❌ No |  |
| `box-shadow` | ❌ No | ⚠ Partial[1] | 1. IE9 does not support this property.
 |

### Date pickers

See the `date` and `time` input types. Many properties are supported, but there is too much inconsistency between browsers to be reliable.

| Property | N | T | Note |
| --- | --- | --- | --- |
| *CSS box model* |
| --- |
| `width` | ❌ No | ❌ No |  |
| `height` | ❌ No | ❌ No |  |
| `border` | ❌ No | ❌ No |  |
| `margin` | ✅ Yes | ✅ Yes |  |
| `padding` | ❌ No | ❌ No |  |
| *Text and font* |
| --- |
| `color` | ❌ No | ❌ No |  |
| `font` | ❌ No | ❌ No |  |
| `letter-spacing` | ❌ No | ❌ No |  |
| `text-align` | ❌ No | ❌ No |  |
| `text-decoration` | ❌ No | ❌ No |  |
| `text-indent` | ❌ No | ❌ No |  |
| `text-overflow` | ❌ No | ❌ No |  |
| `text-shadow` | ❌ No | ❌ No |  |
| `text-transform` | ❌ No | ❌ No |  |
| *Border and background* |
| --- |
| `background` | ❌ No | ❌ No |  |
| `border-radius` | ❌ No | ❌ No |  |
| `box-shadow` | ❌ No | ❌ No |  |

### Color pickers

See the `color` input type:

| Property | N | T | Note |
| --- | --- | --- | --- |
| *CSS box model* |
| --- |
| `width` | ✅ Yes | ✅ Yes |  |
| `height` | ❌ No[1] | ✅ Yes | 1. Opera handles this like a select widget with the same restriction.
 |
| `border` | ✅ Yes | ✅ Yes |  |
| `margin` | ✅ Yes | ✅ Yes |  |
| `padding` | ❌ No[1] | ✅ Yes | 1. Opera handles this like a select widget with the same restriction.
 |
| *Text and font* |
| --- |
| `color` | N.A. | N.A. |  |
| `font` | N.A. | N.A. |  |
| `letter-spacing` | N.A. | N.A. |  |
| `text-align` | N.A. | N.A. |  |
| `text-decoration` | N.A. | N.A. |  |
| `text-indent` | N.A. | N.A. |  |
| `text-overflow` | N.A. | N.A. |  |
| `text-shadow` | N.A. | N.A. |  |
| `text-transform` | N.A. | N.A. |  |
| *Border and background* |
| --- |
| `background` | ❌ No[1] | ❌ No[1] | 1. Supported, but there is too much inconsistency between browsers to
 be reliable.
 |
| `border-radius` | ❌ No[1] | ❌ No[1] |
| `box-shadow` | ❌ No[1] | ❌ No[1] |

### Meters and progress

See the ``<meter>`` and ``<progress>`` elements:

| Property | N | T | Note |
| --- | --- | --- | --- |
| *CSS box model* |
| --- |
| `width` | ✅ Yes | ✅ Yes |  |
| `height` | ✅ Yes | ✅ Yes |  |
| `border` | ⚠ Partial | ✅ Yes |  |
| `margin` | ✅ Yes | ✅ Yes |  |
| `padding` | ✅ Yes | ⚠ Partial[1] | 1. Chrome hides the
 ``<progress>`` and
 ``<meter>`` element when the
 `padding` property is applied on a tweaked
 element.
 |
| *Text and font* |
| --- |
| `color` | N.A. | N.A. |  |
| `font` | N.A. | N.A. |  |
| `letter-spacing` | N.A. | N.A. |  |
| `text-align` | N.A. | N.A. |  |
| `text-decoration` | N.A. | N.A. |  |
| `text-indent` | N.A. | N.A. |  |
| `text-overflow` | N.A. | N.A. |  |
| `text-shadow` | N.A. | N.A. |  |
| `text-transform` | N.A. | N.A. |  |
| *Border and background* |
| --- |
| `background` | ❌ No[1] | ❌ No[1] | 1. Supported, but there is too much inconsistency between browsers to
 be reliable.
 |
| `border-radius` | ❌ No[1] | ❌ No[1] |
| `box-shadow` | ❌ No[1] | ❌ No[1] |

### Range

See the `range` input type. There is no standard way to change the style of the range grip and Opera has no way to tweak the default rendering of the range widget.

| Property | N | T | Note |
| --- | --- | --- | --- |
| *CSS box model* |
| --- |
| `width` | ✅ Yes | ✅ Yes |  |
| `height` | ⚠ Partial[1] | ⚠ Partial[1] | 1. Chrome and Opera add some extra space around the widget.
 |
| `border` | ❌ No | ✅ Yes |  |
| `margin` | ✅ Yes | ✅ Yes |  |
| `padding` | ⚠ Partial[1] | ✅ Yes | 1. The `padding` is applied, but has no visual
 effect.
 |
| *Text and font* |
| --- |
| `color` | N.A. | N.A. |  |
| `font` | N.A. | N.A. |  |
| `letter-spacing` | N.A. | N.A. |  |
| `text-align` | N.A. | N.A. |  |
| `text-decoration` | N.A. | N.A. |  |
| `text-indent` | N.A. | N.A. |  |
| `text-overflow` | N.A. | N.A. |  |
| `text-shadow` | N.A. | N.A. |  |
| `text-transform` | N.A. | N.A. |  |
| *Border and background* |
| --- |
| `background` | ❌ No[1] | ❌ No[1] | 1. Supported, but there is too much inconsistency between browsers to
 be reliable.
 |
| `border-radius` | ❌ No[1] | ❌ No[1] |
| `box-shadow` | ❌ No[1] | ❌ No[1] |

### Image buttons

See the `image` input type:

| Property | N | T | Note |
| --- | --- | --- | --- |
| *CSS box model* |
| --- |
| `width` | ✅ Yes | ✅ Yes |  |
| `height` | ✅ Yes | ✅ Yes |  |
| `border` | ✅ Yes | ✅ Yes |  |
| `margin` | ✅ Yes | ✅ Yes |  |
| `padding` | ✅ Yes | ✅ Yes |  |
| *Text and font* |
| --- |
| `color` | N.A. | N.A. |  |
| `font` | N.A. | N.A. |  |
| `letter-spacing` | N.A. | N.A. |  |
| `text-align` | N.A. | N.A. |  |
| `text-decoration` | N.A. | N.A. |  |
| `text-indent` | N.A. | N.A. |  |
| `text-overflow` | N.A. | N.A. |  |
| `text-shadow` | N.A. | N.A. |  |
| `text-transform` | N.A. | N.A. |  |
| *Border and background* |
| --- |
| `background` | ✅ Yes | ✅ Yes |  |
| `border-radius` | ⚠ Partial[1] | ⚠ Partial[1] | 1. IE9 does not support this property.
 |
| `box-shadow` | ⚠ Partial[1] | ⚠ Partial[1] | 1. IE9 does not support this property.
 |

See also
--------

### Learning path

* Your first HTML form
* How to structure an HTML form
* The native form widgets
* HTML5 input types
* Additional form controls
* UI pseudo-classes
* Styling HTML forms
* Form data validation
* Sending form data
### Advanced Topics

* Sending forms through JavaScript
* How to build custom form widgets
* HTML forms in legacy browsers
* Advanced styling for HTML forms
* **Property compatibility table for form widgets**


Styling web forms - Learn web development

Styling web forms
=================

* Previous
* Overview: Forms
* Next

In the previous few articles, we showed how to create web forms in HTML. Now, we'll show how to style them in CSS.

|  |  |
| --- | --- |
| Prerequisites: |
 A basic understanding of
 HTML and
 CSS.
  |
| Objective: |
 To understand the issues behind styling forms, and learn some of the
 basic styling techniques that will be useful to you.
  |

Challenges in styling form widgets
----------------------------------

### History

In 1995, the HTML 2 specification introduced form controls (a.k.a. "form widgets", or "form elements"). But CSS wasn't released until late 1996, and wasn't supported by most browsers until years afterward; so, in the interim, browsers relied on the underlying operating system to render form widgets.

Even with CSS available, browser vendors were reluctant at first to make form elements stylable, because users were so accustomed to the looks of their respective browsers. But things have changed, and forms widgets are now mostly stylable, with a few exceptions.

### Types of widgets

#### Easy-to-style

1. `<form>`
2. `<fieldset>` and `<legend>`
3. Single-line text `<input>`s (e.g. type text, url, email), except for `<input type="search">`.
4. Multi-line `<textarea>`
5. Buttons (both `<input>` and `<button>`)
6. `<label>`
7. `<output>`

#### Harder-to-style

* Checkboxes and radio buttons
* `<input type="search">`

The article Advanced form styling shows how to style these.

#### Having internals can't be styled in CSS alone

* `<input type="color">`
* Date-related controls such as `<input type="datetime-local">`
* `<input type="range">`
* `<input type="file">`
* Elements involved in creating dropdown widgets, including `<select>`, `<option>`, `<optgroup>` and `<datalist>`.
* `<progress>` and `<meter>`

For example, the date picker calendar, and the button on <select> that displays an options list when clicked, can't be styled using CSS alone.

The articles Advanced form styling and How to build custom form controls describe how to style these.

**Note:** some proprietary CSS pseudo-elements, such as `::-moz-range-track`, are capable of styling such internal components, but these aren't consistent across browsers, so aren't very reliable. We will mention these later.

Styling simple form widgets
---------------------------

The "easy-to-style" widgets in the previous section may be styled using techniques from the articles Your first form and CSS building blocks. There are also special selectors — UI pseudo-classes — that enable styling based on the current state of the UI.

We'll walk through an example at the end of this article — but first, here are some special aspects of form styling that are worth knowing about.

### Fonts and text

CSS font and text features can be used easily with any widget (and yes, you can use `@font-face` with form widgets). However, browser behavior is often inconsistent. By default, some widgets do not inherit `font-family` and `font-size` from their parents. Many browsers use the system's default appearance instead. To make your forms' appearance consistent with the rest of your content, you can add the following rules to your stylesheet:

```css
button,
input,
select,
textarea {
  font-family: inherit;
  font-size: 100%;
}
```

The `inherit` property value causes the property value to match the computed value of the property of its parent element; inheriting the value of the parent.

The screenshots below show the difference. On the left is the default rendering of an `<input type="text">`, `<input type="date">`, `<select>`, `<textarea>`, `<input type="submit">`, and a `<button>` in Chrome on macOS, with the platform's default font style in use. On the right are the same elements, with our above style rule applied.

![Form controls with default and inherited font families. By default, some types are serif and others are sans serif. Inheriting should change the fonts of all to the parent's font family - in this case a paragraph. Oddly, input of type submit does not inherit from the parent paragraph.](/en-US/docs/Learn/Forms/Styling_web_forms/forms_fontfamily.png)

The defaults differed in a number of ways. Inheriting should change their fonts to that of the parent's font family — in this case, the default serif font of the parent container. They all do, with a strange exception — `<input type="submit">` does not inherit from the parent paragraph in Chrome. Rather, it uses the `font-family: system-ui`. This is another reason to use `<button>` elements over their equivalent input types!

There's a lot of debate as to whether forms look better using the system default styles, or customized styles designed to match your content. This decision is yours to make, as the designer of your site, or web application.

### Box sizing

All text fields have complete support for every property related to the CSS box model, such as `width`, `height`, `padding`, `margin`, and `border`. As before, however, browsers rely on the system default styles when displaying these widgets. It's up to you to define how you wish to blend them into your content. If you want to keep the native look and feel of the widgets, you'll face a little difficulty if you want to give them a consistent size.

**This is because each widget has its own rules for border, padding, and margin.** To give the same size to several different widgets, you can use the `box-sizing` property along with some consistent values for other properties:

```css
input,
textarea,
select,
button {
  width: 150px;
  padding: 0;
  margin: 0;
  box-sizing: border-box;
}
```

In the screenshot below, the left column shows the default rendering of an `<input type="radio">`, `<input type="checkbox">`, `<input type="range">`, `<input type="text">`, `<input type="date">`, `<select>`, `<textarea>`, `<input type="submit">`, and `<button>`. The right column on the other hand shows the same elements with our above rule applied to them. Notice how this lets us ensure that all of the elements occupy the same amount of space, despite the platform's default rules for each kind of widget.

![box model properties effect most input types.](/en-US/docs/Learn/Forms/Styling_web_forms/boxmodel_formcontrols1.png)

What may not be apparent via the screenshot is that the radio and checkbox controls still look the same, but they are centered in the 150px of horizontal space provided by the `width` property. Other browsers may not center the widgets, but they do adhere to the space allotted.

### Legend placement

The `<legend>` element is okay to style, but it can be a bit tricky to control the placement of it. By default, it is always positioned over the top border of its `<fieldset>` parent, near the top left corner. To position it somewhere else, for example inside the fieldset somewhere, or near the bottom left corner, you need to rely on the positioning.

Take the following example:

To position the legend in this manner, we used the following CSS (other declarations removed for brevity):

```css
fieldset {
  position: relative;
}

legend {
  position: absolute;
  bottom: 0;
  right: 0;
}
```

The `<fieldset>` needs to be positioned too, so that the `<legend>` is positioned relative to it (otherwise the `<legend>` would be positioned relative to the `<body>`).

The `<legend>` element is very important for accessibility — it will be spoken by assistive technologies as part of the label of each form element inside the fieldset — but using a technique like the one above is fine. The legend contents will still be spoken in the same way; it is just the visual position that has changed.

**Note:** You could also use the `transform` property to help you with positioning your `<legend>`. However, when you position it with for example a `transform: translateY();`, it moves but leaves an ugly gap in the `<fieldset>` border, which is not easy to get rid of.

A specific styling example
--------------------------

Let's look at a concrete example of how to style an HTML form. We will build a fancy-looking "postcard" contact form; see here for the finished version.

If you want to follow along with this example, make a local copy of our postcard-start.html file, and follow the below instructions.

### The HTML

The HTML is only slightly more involved than the example we used in the first article of this guide; it just has a few extra IDs and a heading.

```html
<form>
  <h1>to: Mozilla</h1>

  <div id="from">
    <label for="name">from:</label>
    <input type="text" id="name" name="user\_name" />
  </div>

  <div id="reply">
    <label for="mail">reply:</label>
    <input type="email" id="mail" name="user\_email" />
  </div>

  <div id="message">
    <label for="msg">Your message:</label>
    <textarea id="msg" name="user\_message"></textarea>
  </div>

  <div class="button">
    <button type="submit">Send your message</button>
  </div>
</form>
```

Add the above code into the body of your HTML.

### Organizing your assets

This is where the fun begins! Before we start coding, we need three additional assets:

1. The postcard background — download this image and save it in the same directory as your working HTML file.
2. A typewriter font: The "Mom's Typewriter" font from dafont.com — download the TTF file into the same directory as above.
3. A hand-drawn font: The "Journal" font from dafont.com — download the TTF file into the same directory as above.

Your fonts need some more processing before you start:

1. Go to the fontsquirrel.com Webfont Generator.
2. Using the form, upload both your font files and generate a webfont kit. Download the kit to your computer.
3. Unzip the provided zip file.
4. Inside the unzipped contents you will find some font files (at the time of writing, two `.woff` files and two `.woff2` files; they might vary in the future.) Copy these files into a directory called fonts, in the same directory as before. We are using two different files for each font to maximize browser compatibility; see our Web fonts article for a lot more information.
### The CSS

Now we can dig into the CSS for the example. Add all the code blocks shown below inside the `<style>` element, one after another.

#### Overall layout

First, we prepare by defining our `@font-face` rules, and all the basic styles set on the `<body>` and `<form>` elements. If the fontsquirrel output was different from what we described above, you can find the correct `@font-face` blocks inside your downloaded webfont kit, in the `stylesheet.css` file (you'll need to replace the below `@font-face` blocks with them, and update the paths to the font files):

```css
@font-face {
  font-family: "handwriting";
  src:
    url("fonts/journal-webfont.woff2") format("woff2"),
    url("fonts/journal-webfont.woff") format("woff");
  font-weight: normal;
  font-style: normal;
}

@font-face {
  font-family: "typewriter";
  src:
    url("fonts/momot\_\_\_-webfont.woff2") format("woff2"),
    url("fonts/momot\_\_\_-webfont.woff") format("woff");
  font-weight: normal;
  font-style: normal;
}

body {
  font: 1.3rem sans-serif;
  padding: 0.5em;
  margin: 0;
  background: #222;
}

form {
  position: relative;
  width: 740px;
  height: 498px;
  margin: 0 auto;
  padding: 1em;
  box-sizing: border-box;
  background: #fff url(background.jpg);

  /\* we create our grid \*/
  display: grid;
  grid-gap: 20px;
  grid-template-columns: repeat(2, 1fr);
  grid-template-rows: 10em 1em 1em 1em;
}
```

Notice that we've used some CSS Grid and Flexbox to lay out the form. Using this we can easily position our elements, including the title and all the form elements:

```css
h1 {
  font:
    1em "typewriter",
    monospace;
  align-self: end;
}

#message {
  grid-row: 1 / 5;
}

#from,
#reply {
  display: flex;
}
```

#### Labels and controls

Now we can start working on the form elements themselves. First, let's ensure that the `<label>`s are given the right font:

```css
label {
  font:
    0.8em "typewriter",
    sans-serif;
}
```

The text fields require some common rules. In other words, we remove their `borders` and `backgrounds`, and redefine their `padding` and `margin`:

```css
input,
textarea {
  font:
    1.4em/1.5em "handwriting",
    cursive,
    sans-serif;
  border: none;
  padding: 0 10px;
  margin: 0;
  width: 80%;
  background: none;
}
```

When one of these fields gains focus, we highlight them with a light grey, transparent, background (it is always important to have focus style, for usability and keyboard accessibility):

```css
input:focus,
textarea:focus {
  background: rgb(0 0 0 / 10%);
  border-radius: 5px;
}
```

Now that our text fields are complete, we need to adjust the display of the single and multiple-line text fields to match, since they won't typically look the same using the defaults.

#### Tweaking the textareas

`<textarea>` elements default to being rendered as an inline-block element. The two important things here are the `resize` and `overflow` properties. While our design is a fixed-size design, and we could use the `resize` property to prevent users from resizing our multi-line text field, it is best to not prevent users from resizing a textarea if they so choose. The `overflow` property is used to make the field render more consistently across browsers. Some browsers default to the value `auto`, while some default to the value `scroll`. In our case, it's better to be sure everyone will use `auto`:

```css
textarea {
  display: block;

  padding: 10px;
  margin: 10px 0 0 -10px;
  width: 100%;
  height: 90%;

  border-right: 1px solid;

  /\* resize : none; \*/
  overflow: auto;
}
```

#### Styling the submit button

The `<button>` element is really convenient to style with CSS; you can do whatever you want, even using pseudo-elements:

```css
button {
  padding: 5px;
  font: bold 0.6em sans-serif;
  border: 2px solid #333;
  border-radius: 5px;
  background: none;
  cursor: pointer;
  transform: rotate(-1.5deg);
}

button:after {
  content: " >>>";
}

button:hover,
button:focus {
  background: #000;
  color: #fff;
}
```

### The final result

And voilà! Your form should now look like this:

![The final look and layout of the form after applying all styling and tweaking to it as described above](/en-US/docs/Learn/Forms/Styling_web_forms/updated-form-screenshot.jpg)

**Note:** If your example does not work quite as you expected and you want to check it against our version, you can find it on GitHub — see it running live (also see the source code).

Test your skills
----------------

You've reached the end of this article, but can you remember the most important information? You can find some further tests to verify that you've retained this information before you move on — see Test your skills: Styling basics.

Summary
-------

As you can see, as long as we want to build forms with just text fields and buttons, it's easy to style them using CSS. In the next article, we will see how to handle form widgets which fall into the "bad" and "ugly" categories.

* Previous
* Overview: Forms
* Next
### Advanced Topics

* How to build custom form controls
* Sending forms through JavaScript
* Property compatibility table for form widgets