Chapter 5: Add CSS File to the Project
Add CSS File to the Project: Many beginners keep writing styles directly inside <style> tags in base.html or in individual templates — which works for 5 minutes, but becomes a complete nightmare when you want to change colors, fonts, button shapes or spacing later.
Today we are going to do it the right way from the very beginning: one clean global CSS file + optional app-specific CSS, properly linked, working in development, and ready for production.
We will go very slowly, step-by-step, like pair-programming — I’ll explain every folder, every line, every setting, and we’ll test together so you see exactly what happens.
Goal for This Lesson
By the end you will have:
- A global CSS file that applies to every page
- An optional app-specific CSS file for polls pages
- Everything linked correctly from base.html
- Styles visible on all pages
- No 404 errors
- Ready for production (collectstatic)
Step 1 – Create the Folders (Very Important Structure)
In your project root (next to manage.py):
|
0 1 2 3 4 5 6 7 8 |
mkdir -p static/css mkdir -p static/js # we'll use later mkdir -p static/images # for logo, favicon, etc. |
Now create app-specific static folder (recommended best practice):
|
0 1 2 3 4 5 6 7 8 |
mkdir -p polls/static/polls/css mkdir -p polls/static/polls/js mkdir -p polls/static/polls/images |
Why the extra polls/ inside static/? → Namespacing. If you later add a blog app that also has style.css, Django knows which one belongs to which app.
Final structure:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
mysite-project/ ├── manage.py ├── static/ ← global / project-level static │ ├── css/ │ │ └── global.css ← main file we will create now │ ├── js/ │ └── images/ ├── polls/ │ └── static/ │ └── polls/ ← app namespace │ ├── css/ │ │ └── polls.css ← optional – only for polls pages │ ├── js/ │ └── images/ └── ... |
Step 2 – Tell Django Where to Find These Files
Open mysite/settings.py
Make sure (or add) these lines:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
from pathlib import Path BASE_DIR = Path(__file__).resolve().parent.parent # Static files (CSS, JavaScript, Images) STATIC_URL = '/static/' # URL prefix in browser # Extra locations where Django should look for static files (development) STATICFILES_DIRS = [ BASE_DIR / 'static', # ← this line adds your global static folder ] # Where collectstatic will copy everything in production STATIC_ROOT = BASE_DIR / 'staticfiles' # Recommended: compression + cache-busting (with WhiteNoise) STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage' |
Important — without STATICFILES_DIRS, Django will not look in your static/ folder.
Step 3 – Create the Global CSS File
File: static/css/global.css
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
/* Global styles — applied to every single page via base.html */ :root { --primary: #006400; /* dark green – Telangana / Hyderabad vibe */ --primary-dark: #004d00; --light-bg: #f8f9fa; --text: #2d2d2d; --gray: #6c757d; --danger: #dc3545; --success: #28a745; } * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: system-ui, -apple-system, BlinkMacSystemFont, sans-serif; background: var(--light-bg); color: var(--text); line-height: 1.6; } .container { max-width: 1100px; margin: 0 auto; padding: 0 1.5rem; } header { background: var(--primary); color: white; padding: 1.5rem 0; text-align: center; box-shadow: 0 2px 10px rgba(0,0,0,0.15); } header img { height: 50px; vertical-align: middle; margin-right: 1rem; } h1, h2, h3 { margin-bottom: 1rem; } .btn { display: inline-block; padding: 0.8rem 1.6rem; background: var(--primary); color: white; border-radius: 6px; text-decoration: none; font-weight: 500; transition: background 0.2s; } .btn:hover { background: var(--primary-dark); } .card { background: white; padding: 1.8rem; border-radius: 10px; box-shadow: 0 4px 15px rgba(0,0,0,0.08); margin-bottom: 1.5rem; } /* Responsive adjustments */ @media (max-width: 768px) { header h1 { font-size: 1.8rem; } } |
This is a clean, modern base — you can expand it later.
Step 4 – (Optional) App-Specific CSS
File: polls/static/polls/css/polls.css
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
/* Styles only needed on polls pages */ .poll-card { border-left: 5px solid var(--primary); transition: transform 0.2s; } .poll-card:hover { transform: translateY(-4px); } .vote-badge { background: var(--success); color: white; padding: 4px 10px; border-radius: 20px; font-size: 0.85rem; font-weight: 500; } |
Step 5 – Link the Files in base.html
Open templates/base.html
Add at the very top (after {% extends %} if you have it):
|
0 1 2 3 4 5 6 |
{% load static %} |
Then in <head>:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<!-- Global CSS – applies to every page --> <link rel="stylesheet" href="{% static 'css/global.css' %}"> <!-- App-specific CSS – only load when needed --> {% if request.resolver_match.app_name == 'polls' %} <link rel="stylesheet" href="{% static 'polls/css/polls.css' %}"> {% endif %} <!-- Page-specific CSS block --> {% block extra_css %}{% endblock %} |
Step 6 – Use the Styles in Your Templates
In polls/templates/polls/index.html (example):
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
{% extends "base.html" %} {% block content %} <div class="container"> <div class="card poll-card"> <h2>Latest Polls</h2> <!-- your loop --> {% for question in latest_questions %} <div class="card"> <h3>{{ question.question_text }}</h3> <span class="vote-badge">{{ question.vote_count|default:0 }} vote{{ question.vote_count|pluralize }}</span> </div> {% endfor %} </div> </div> {% endblock %} |
→ Now all pages have global styles + polls pages get extra poll-specific styles
Step 7 – Test It
- Save all files
- Restart server if needed: python manage.py runserver
- Open /polls/ or any page
- Check:
- Background color, font, button styles from global.css
- Poll-card border & hover effect from polls.css
- No 404 in browser console (F12 → Network tab)
If 404 still happens:
- Forgot {% load static %} → add at top
- Wrong path → check exactly static/css/global.css (case-sensitive!)
- Server not restarted after adding files
Step 8 – Production Step (When You Deploy)
Run once before deploy:
|
0 1 2 3 4 5 6 |
python manage.py collectstatic --noinput |
→ All files copied to staticfiles/
WhiteNoise (if configured) serves them automatically.
Your Quick Task Right Now (Do This – It Will Stick)
- Create static/css/global.css with the code above
- Add {% load static %} + <link href=”{% static ‘css/global.css’ %}”> to base.html
- Restart server → check if header, body, buttons look different
- Create polls/static/polls/css/polls.css → add .poll-card { border: 3px solid green; }
- In index.html → add class poll-card to a div → see the border
- Check browser console → confirm no 404
Tell me what feels next:
- “Done! Now show me how to add Tailwind CSS (CDN or local)”
- “How to add a favicon and apple-touch-icon properly?”
- “What if I want to use SCSS / PostCSS / Tailwind build step?”
- “Got 404 or style not applying – here’s screenshot/error”
- Or finally ready for Django Forms + Voting + POST + F() + results page
You now have global + app-specific CSS working beautifully — your site is starting to look like a real, modern web app.
You’re doing really well — let’s keep going! 🚀🇮🇳
