The TailPySCSS Bible
The complete manual for Python developers building production-grade UIs without Node.js. Master the framework, learn the patterns, and ship beautiful applications.
Why TailPySCSS?
Modern web development is trapped in the JavaScript ecosystem. To style a Python web app, you're forced to install Node.js, manage npm packages, configure Webpack, and deal with PostCSS. This is absurd for backend developers.
TailPySCSS breaks this dependency chain. It's a pure-Python CSS framework that gives you the power of utility classes (like Tailwind) and SCSS composition (like Bootstrap), but with zero JavaScript tooling.
- π« No Node.js: No
npm, nopackage.json, nonode_modulesfolder weighing 300MB. - β‘ Zero Runtime: It's a build-time tool. Scans your Python/HTML files and generates static CSS.
- π Framework Agnostic: Works with Flask, Django, FastAPI, Streamlit, or even static HTML.
- π¨ SCSS Power: Built on LibSass. Use variables, nesting, mixins, and the full power of SCSS.
- π² Tree Shaking: Only generates CSS for classes you actually use.
Installation
TailPySCSS is distributed via PyPI. Install it in your virtual environment:
$ pip install tailpyscss
Verify the installation:
$ tailpyscss --version tailpyscss, version 0.5.3
β Best Practice: Virtual Environments
Always use a virtual environment (venv or conda) to isolate project
dependencies. This prevents version conflicts across projects.
CLI User Guide
The TailPySCSS CLI has three essential commands. Understanding each is critical to a smooth workflow.
Command: tailpyscss init
Run this once in your project root. It scaffolds the directory structure and creates configuration files.
$ tailpyscss init [*] Created static/css/ [*] Created styles/main.scss [*] Created tailpy_config.py β Initialization complete!
What Gets Created?
static/css/styles.css- The generated CSS output (served to your app).styles/main.scss- Your custom SCSS entry point.tailpy_config.py- Configuration file (colors, spacing, screens).
β οΈ Warning: Running Init Multiple Times
Running init again will overwrite styles/main.scss and
tailpy_config.py. Always back up custom code first!
Command: tailpyscss watch
Use this during development. It watches your project for file changes and rebuilds CSS automatically (Hot Reloading).
$ tailpyscss watch [*] Watching for changes in .py, .html, .scss files... [+] Rebuilt CSS in 0.03s [*] Detected change in app.py [+] Rebuilt CSS in 0.02s
How It Works
- Monitors all
.py,.html, and.scssfiles. - On change, it rescans for utility classes and reruns the SCSS compiler.
- Outputs updated
styles.css.
β Best Practice: Keep Watch Running
Run watch in a dedicated terminal window. This ensures you always see your CSS
changes reflected instantly.
Command: tailpyscss build
Use this for production deployments. It performs a one-time optimized build without watching.
$ tailpyscss build Tree Shaking Active: Found 42 contexts Build complete. Output: static/css/styles.css
When to Use This
- In your Dockerfile (during the build step).
- In CI/CD pipelines (GitHub Actions, GitLab CI).
- Before committing to version control (to ensure CSS is up-to-date).
Methodology: The Pro Way (BEM + Utilities)
For scalable, maintainable projects, we recommend the BEM (Block Element Modifier) methodology combined with utility classes for modifiers.
What is BEM?
BEM is a naming convention for CSS classes that enforces semantic structure:
- Block: A standalone component (
.card). - Element: A part of the block (
.card__header,.card__body). - Modifier: A variation of the block (
.card--featured).
β Best Practice: BEM for Structure, Utilities for State
Use BEM classes to define the structure of your component (layout, hierarchy). Use utility classes for state (colors, spacing, typography).
Example: Clean Card Component
HTML (Readable)
<div class="card card--featured">
<div class="card__header">
<h2 class="text-xl font-bold text-gray-900">
Article Title
</h2>
</div>
<div class="card__body">
<p class="text-gray-600">
This is a clean, readable structure.
</p>
</div>
<div class="card__footer">
<button class="bg-indigo-600 text-white px-4 py-2 rounded">
Read More
</button>
</div>
</div>
SCSS (styles/_components.scss)
.card {
background: white;
border-radius: 0.75rem;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
overflow: hidden;
&--featured {
border: 2px solid #4f46e5;
}
&__header {
padding: 1.5rem;
border-bottom: 1px solid #e5e7eb;
}
&__body {
padding: 1.5rem;
}
&__footer {
padding: 1rem 1.5rem;
background: #f9fafb;
}
}
Why This Works
- Readable HTML: At a glance, you understand the component hierarchy.
- Scoped Styles: All
.cardstyles are isolated in one SCSS block. - Reusability: The
.cardcan be reused across the app. - Utilities for Flexibili ty: You still get to use
text-xl,font-bold, etc. for quick tweaks.
Methodology: The Rapid Way (100% Utility Classes)
For prototypes, dashboards, or simple one-off pages, you can skip SCSS entirely and use only utility classes.
<!-- Utility-Only Approach -->
<div class="bg-white rounded-lg shadow p-6">
<h2 class="text-2xl font-bold text-gray-900 mb-4">Quick Prototype</h2>
<p class="text-gray-600 mb-6">Built in 5 minutes.</p>
<button class="bg-indigo-600 text-white px-6 py-2 rounded hover:bg-indigo-700 transition">
Click Me
</button>
</div>
β οΈ Warning: Readability at Scale
This approach works great for small pages. But for complex UIs with 20+ components, it becomes unmaintainable. You'll end up with 50-class divs that are impossible to debug.
When to Use Utility-Only
- Admin dashboards (internal tools).
- Landing pages (one-off marketing pages).
- Prototypes (testing ideas quickly).
Mixing Approaches: BEM + UI Components + Utilities
The ultimate workflow combines all three: BEM for custom components,
ui="..." for framework components, and utilities for modifiers.
<!-- Hybrid Approach -->
<div class="profile-card">
<!-- Framework Component -->
<button ui="button" class="bg-indigo-600 text-white">Follow</button>
<!-- BEM Component -->
<div class="profile-card__avatar">
<img src="user.jpg" class="w-16 h-16 rounded-full">
</div>
<!-- Utility Classes -->
<h3 class="text-lg font-bold text-gray-900">John Doe</h3>
<p class="text-sm text-gray-600">Software Engineer</p>
</div>
β Best Practice: Use What Fits
Don't be dogmatic. Use BEM for complex components, framework components where they fit, and utilities everywhere else.
π« Anti-Patterns to Avoid
Learn from these common mistakes to keep your codebase clean.
β Anti-Pattern #1: The "Div Soup"
Using utility classes to build deeply nested layouts without any semantic naming. This is debugging hell.
<!-- BAD: What does this even do? -->
<div class="flex flex-col items-center justify-center p-6 bg-white rounded-lg shadow">
<div class="flex items-center gap-4">
<div class="w-12 h-12 bg-indigo-600 rounded-full"></div>
<div class="flex flex-col">
<div class="text-lg font-bold">?????</div>
<div class="text-sm text-gray-600">?????</div>
</div>
</div>
</div>
β Anti-Pattern #2: Ignoring SCSS
TailPySCSS gives you the full power of SCSS. Use it! Don't be afraid to write actual CSS for animations, transitions, or complex selectors.
β Anti-Pattern #3: Not Using tailpy_config.py
Hardcoding colors like #4f46e5 in your HTML instead of defining them in
tailpy_config.py as primary. This makes theming impossible.
πΆοΈ Integration: Flask
Flask works out-of-the-box with TailPySCSS. No configuration neededβjust point your templates at the generated CSS.
Step 1: Directory Structure
my_flask_app/ βββ app.py βββ templates/ β βββ base.html β βββ index.html βββ static/ β βββ css/ β βββ styles.css <-- TailPySCSS outputs here βββ styles/ β βββ main.scss <-- Your custom SCSS βββ tailpy_config.py
Step 2: Base Template (base.html)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{% block title %}My App{% endblock %}</title>
<link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}">
</head>
<body class="bg-gray-50">
<nav class="bg-white shadow">
<div class="max-w-7xl mx-auto px-4 py-4">
<a href="/" class="text-xl font-bold text-gray-900">MyApp</a>
</div>
</nav>
<main class="max-w-7xl mx-auto px-4 py-8">
{% block content %}{% endblock %}
</main>
</body>
</html>
Step 3: Run Flask + Watch
In one terminal, run your Flask app. In another, run tailpyscss watch:
# Terminal 1 $ flask run # Terminal 2 $ tailpyscss watch
β Production Tip: Flask + Docker
In your Dockerfile, run tailpyscss build before starting the Flask server. This
ensures the CSS is always up-to-date.
Production Dockerfile Example
FROM python:3.11-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . # Build CSS RUN tailpyscss build # Run Flask CMD ["gunicorn", "-b", "0.0.0.0:8000", "app:app"]
πΈ Integration: Django
Django requires explicit static file configuration, but once set up, it works seamlessly.
Step 1: Configure Static Files (settings.py)
# settings.py
import os
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent
STATIC_URL = '/static/'
STATICFILES_DIRS = [
BASE_DIR / "static", # Where TailPySCSS outputs
]
# For production (collectstatic)
STATIC_ROOT = BASE_DIR / "staticfiles"
Step 2: Base Template (base.html)
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{% block title %}Django App{% endblock %}</title>
<link rel="stylesheet" href="{% static 'css/styles.css' %}">
</head>
<body class="bg-gray-50">
{% block content %}{% endblock %}
</body>
</html>
Step 3: Production Deployment
Before deploying, run both tailpyscss build and collectstatic:
# Build CSS $ tailpyscss build # Collect static files (for Nginx/Apache) $ python manage.py collectstatic --noinput
β οΈ Common Pitfall: Forgetting `collectstatic`
In production, Django serves static files from STATIC_ROOT, not
STATICFILES_DIRS. Always run collectstatic after building CSS!
β‘ Integration: FastAPI
FastAPI doesn't serve static files by default. You need to mount a StaticFiles directory
and use Jinja2 templates.
Step 1: Install Dependencies
$ pip install fastapi uvicorn jinja2 python-multipart
Step 2: App Structure
fastapi_app/ βββ main.py βββ templates/ β βββ index.html βββ static/ β βββ css/ β βββ styles.css βββ tailpy_config.py
Step 3: FastAPI App (main.py)
from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
app = FastAPI()
# Mount static files
app.mount("/static", StaticFiles(directory="static"), name="static")
# Setup Jinja2
templates = Jinja2Templates(directory="templates")
@app.get("/", response_class=HTMLResponse)
async def read_root(request: Request):
return templates.TemplateResponse("index.html", {"request": request})
Step 4: Template (templates/index.html)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>FastAPI + TailPySCSS</title>
<link rel="stylesheet" href="/static/css/styles.css">
</head>
<body class="bg-gray-50 p-8">
<h1 class="text-3xl font-bold text-gray-900">Hello FastAPI!</h1>
</body>
</html>
Step 5: Run
# Terminal 1 $ uvicorn main:app --reload # Terminal 2 $ tailpyscss watch
π Integration: Streamlit
Streamlit apps don't support <link> tags natively. Instead, we inject CSS
directly using st.markdown.
Step 1: Load CSS Helper
import streamlit as st
def load_tailpyscss(css_file="static/css/styles.css"):
"""Inject TailPySCSS styles into Streamlit"""
try:
with open(css_file, "r") as f:
st.markdown(f'<style>{f.read()}</style>', unsafe_allow_html=True)
except FileNotFoundError:
st.warning(f"CSS file not found: {css_file}. Run 'tailpyscss build' first!")
# Call at the top of your app
load_tailpyscss()
Step 2: Use Utility Classes
import streamlit as st
load_tailpyscss()
# Custom styled containers
st.markdown('''
<div class="bg-white rounded-lg shadow-lg p-6 mb-4">
<h2 class="text-2xl font-bold text-gray-900 mb-2">Sales Dashboard</h2>
<p class="text-gray-600">Real-time analytics</p>
</div>
''', unsafe_allow_html=True)
# Styled metrics grid
st.markdown('''
<div class="grid grid-cols-3 gap-4">
<div class="bg-indigo-50 p-4 rounded-lg">
<h3 class="text-indigo-600 font-bold">Revenue</h3>
<p class="text-2xl font-bold text-gray-900">$45,231</p>
</div>
</div>
''', unsafe_allow_html=True)
β οΈ Limitation: No Interactive Components
Streamlit's st.button and st.selectbox are pre-styled. You can only
style custom HTML that you inject via st.markdown.
π Integration: Plain HTML
For static sites or vanilla JavaScript apps, just link the generated CSS directly.
Step 1: Build CSS
$ tailpyscss build Build complete. Output: static/css/styles.css
Step 2: Link in HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>My Static Site</title>
<link rel="stylesheet" href="static/css/styles.css">
</head>
<body class="bg-gray-100 p-8">
<div class="max-w-4xl mx-auto bg-white rounded-lg shadow p-8">
<h1 class="text-3xl font-bold text-gray-900 mb-4">Welcome</h1>
<p class="text-gray-600">A simple, beautiful page.</p>
</div>
</body>
</html>
β Pro Tip: Version Your CSS
For static sites, append a version query string to bust browser cache:
<link rel="stylesheet" href="static/css/styles.css?v=1.0.2">
How the Scanning Engine Works
TailPySCSS uses a file-based scanner to detect which utility classes you're using. Here's the process:
- Scan: Recursively walks your project directories for
.py,.html, and.jsfiles. - Extract: Uses regex to find strings that look like CSS classes
(
p-4,text-center,grid-cols-3). - Match: Checks if these match any available generators (Layout, Spacing, Colors, Typography, etc.).
- Generate SCSS: Writes SCSS code for only the classes you actually use (Tree Shaking).
- Compile: LibSass compiles the generated SCSS + your custom SCSS into
styles.css.
Example: What Gets Scanned
# app.py
@app.route("/")
def index():
return render_template("index.html", classes="bg-white p-6 rounded-lg")
# Templates will be scanned for: bg-white, p-6, rounded-lg
Configuration Deep Dive
The tailpy_config.py file controls every aspect of CSS generation. It's pure Python, so
you can script it!
Full Configuration Example
# tailpy_config.py
config = {
# === COLORS ===
"colors": {
"primary": "#4f46e5", # .text-primary, .bg-primary
"secondary": "#ec4899",
"accent": "#f59e0b",
"dark": "#111827",
"light": "#f9fafb",
},
# === SCREENS ===
"screens": {
"sm": "640px",
"md": "768px",
"lg": "1024px",
"xl": "1280px",
"2xl": "1536px",
},
# === SPACING ===
"spacing": {
"0": "0px",
"1": "0.25rem",
"2": "0.5rem",
"4": "1rem",
"6": "1.5rem",
"8": "2rem",
"12": "3rem",
"16": "4rem",
},
}
Dynamic Configuration (Scripted)
Since it's Python, you can generate values dynamically:
# Generate spacing scale automatically
spacing = {}
for i in range(0, 101, 4):
spacing[str(i)] = f"{i * 0.25}rem"
config = {
"spacing": spacing # 0, 4, 8, 12, ... 96, 100
}
Custom SCSS Workflows
TailPySCSS gives you the full power of SCSS. Use it to define reusable components, mixins, and variables.
Example: Using the theme() Function
Access your config values in SCSS using theme():
// styles/main.scss
.my-button {
background-color: theme('colors.primary');
padding: theme('spacing.4');
border-radius: 0.5rem;
&:hover {
background-color: darken(theme('colors.primary'), 10%);
}
}
Importing Custom Files
Organize your SCSS into multiple files and import them:
// styles/main.scss @import "variables"; @import "components/header"; @import "components/footer";
β Best Practice: Partial Files
Prefix partial files with _ (e.g., _variables.scss) so they don't
compile independently.
Production Deployment
For production, always run tailpyscss build in your deployment pipeline.
Docker Example (Multi-Stage Build)
FROM python:3.11-slim AS builder WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . # Build CSS with TailPySCSS RUN tailpyscss build # === PRODUCTION IMAGE === FROM python:3.11-slim WORKDIR /app COPY --from=builder /app /app CMD ["gunicorn", "-b", "0.0.0.0:8000", "app:app"]
GitHub Actions CI/CD
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: '3.11'
- run: pip install -r requirements.txt
- run: tailpyscss build
- name: Deploy to Production
run: |
# Your deployment script here