myfy-frontend
Frontend module for myfy with Tailwind 4, DaisyUI 5, Vite, and Jinja2 templates.
Overview
myfy-frontend provides a complete frontend solution that seamlessly integrates with myfy's web module. It combines modern frontend tooling (Vite, Tailwind 4) with server-side rendering (Jinja2) for a powerful full-stack experience.
Installation
Dependencies:
- myfy-core - Core framework
- myfy-web - Web module
- jinja2 - Template engine
- Node.js and npm (for Vite and Tailwind)
Key Features
Modern CSS with Tailwind 4
- Native CSS engine - Faster builds, no PostCSS
- Better performance - Optimized output
- Modern syntax - CSS variables, container queries
- Zero config - Works out of the box
Component Library with DaisyUI 5
- 50+ components - Buttons, cards, modals, forms
- Semantic themes - Light/dark mode built-in
- Accessibility - WCAG compliant
- Customizable - Full theme control
Lightning-Fast Dev with Vite
- Instant HMR - Sub-100ms hot updates
- Optimized builds - Tree-shaking, code splitting
- Dev server - Fast, reliable, zero config
- Asset handling - Images, fonts, icons
Server-Side Templates with Jinja2
- Familiar syntax - Like Django/Flask templates
- Template inheritance - DRY principles
- Macros - Reusable components
- Auto-reload - Changes reflect immediately
Quick Start
Step 1: Initialize Frontend
Run the init command to scaffold your frontend:
This creates:
- app.py - Ready-to-run application with a home route
- frontend/templates/home.html - Styled welcome page with DaisyUI
- frontend/templates/base.html - Base layout template
- frontend/templates/components/ - Reusable component templates
- frontend/css/ - Tailwind CSS files
- frontend/js/ - JavaScript entry points
- package.json - Node dependencies (Vite, Tailwind 4, DaisyUI 5)
- vite.config.js - Vite configuration
Output:
🎨 Initializing myfy frontend...
✅ Created package.json
✅ Created vite.config.js
✅ Created .gitignore
✅ Created app.py
✅ Created frontend/ directory structure
📦 Installing Node dependencies...
✅ Node dependencies installed!
✨ Frontend initialized successfully!
Next steps:
1. Run 'uv run myfy run' to start your app
2. Edit frontend/templates/ to create your pages
3. Customize frontend/css/input.css for your styles
Step 2: Run Your App
Visit http://127.0.0.1:8000 to see your styled home page!
The generated welcome page with Tailwind 4 and DaisyUI 5 styling
What You Get
The generated app.py contains:
from myfy.core import Application
from myfy.web import WebModule, route
from myfy.frontend import FrontendModule, render_template
from starlette.requests import Request
from starlette.templating import Jinja2Templates
@route.get("/")
async def home(request: Request, templates: Jinja2Templates):
"""Home page."""
return render_template(
"home.html",
request=request,
templates=templates,
title="Welcome to myfy",
)
# Create application
app = Application(auto_discover=False)
app.add_module(WebModule())
app.add_module(FrontendModule())
if __name__ == "__main__":
import asyncio
asyncio.run(app.run())
The generated home.html includes:
- Welcome message with your app name
- DaisyUI buttons demonstrating component usage
- Next steps section guiding you through customization
- Responsive layout with Tailwind utilities
Project Structure
After initialization, your project will have:
your-project/
├── frontend/
│ ├── css/
│ │ └── input.css # Tailwind directives
│ ├── js/
│ │ ├── main.js # Main JavaScript entry
│ │ └── theme-switcher.js # Dark mode toggle
│ ├── templates/
│ │ ├── base.html # Base layout
│ │ ├── home.html # Homepage example
│ │ └── components/
│ │ ├── navbar.html # Navigation component
│ │ ├── footer.html # Footer component
│ │ └── button.html # Button component
│ └── static/
│ └── dist/ # Built assets (gitignored)
├── package.json # Node dependencies
├── vite.config.js # Vite configuration
└── app.py # Your application
Creating Templates
Basic Template
{# templates/home.html #}
{% extends "base.html" %}
{% block content %}
<div class="hero min-h-screen bg-base-200">
<div class="hero-content text-center">
<div class="max-w-md">
<h1 class="text-5xl font-bold">Hello, myfy!</h1>
<p class="py-6">Build Python apps with modern frontend tooling.</p>
<button class="btn btn-primary">Get Started</button>
</div>
</div>
</div>
{% endblock %}
Using Components
{# Import components #}
{% from "components/navbar.html" import navbar %}
{% from "components/footer.html" import footer %}
{% block content %}
{{ navbar(logo="MyApp", links=[
{"text": "Home", "href": "/"},
{"text": "About", "href": "/about"},
{"text": "Contact", "href": "/contact"}
]) }}
<main>
<!-- Your content -->
</main>
{{ footer(year=2025) }}
{% endblock %}
Template Inheritance
{# Base template #}
<!DOCTYPE html>
<html data-theme="light">
<head>
<title>{% block title %}My App{% endblock %}</title>
<link rel="stylesheet" href="{{ url_for('static', path='dist/style.css') }}">
</head>
<body>
{% block content %}{% endblock %}
<script type="module" src="{{ url_for('static', path='dist/main.js') }}"></script>
</body>
</html>
Rendering Templates
Basic Rendering
from myfy.frontend import render_template
from myfy.web import route
@route.get("/")
async def home():
return render_template("home.html")
With Variables
@route.get("/user/{user_id}")
async def user_profile(user_id: int, service: UserService):
user = await service.get_user(user_id)
return render_template(
"user.html",
user=user,
title=f"{user.name}'s Profile"
)
With Complex Data
@route.get("/dashboard")
async def dashboard(service: DashboardService):
data = await service.get_dashboard_data()
return render_template(
"dashboard.html",
stats=data.stats,
recent_items=data.recent_items,
notifications=data.notifications
)
DaisyUI Components
Buttons
<button class="btn">Button</button>
<button class="btn btn-primary">Primary</button>
<button class="btn btn-secondary">Secondary</button>
<button class="btn btn-accent">Accent</button>
<button class="btn btn-ghost">Ghost</button>
<button class="btn btn-link">Link</button>
<!-- Sizes -->
<button class="btn btn-lg">Large</button>
<button class="btn btn-sm">Small</button>
<button class="btn btn-xs">Tiny</button>
Cards
<div class="card bg-base-100 shadow-xl">
<figure>
<img src="/image.jpg" alt="Card image" />
</figure>
<div class="card-body">
<h2 class="card-title">Card Title</h2>
<p>Card description goes here.</p>
<div class="card-actions justify-end">
<button class="btn btn-primary">Action</button>
</div>
</div>
</div>
Forms
<form class="form-control w-full max-w-xs">
<label class="label">
<span class="label-text">Email</span>
</label>
<input type="email" class="input input-bordered w-full" />
<label class="label">
<span class="label-text">Password</span>
</label>
<input type="password" class="input input-bordered w-full" />
<button type="submit" class="btn btn-primary mt-4">Submit</button>
</form>
Modals
<!-- Trigger -->
<button class="btn" onclick="my_modal.showModal()">Open Modal</button>
<!-- Modal -->
<dialog id="my_modal" class="modal">
<div class="modal-box">
<h3 class="font-bold text-lg">Hello!</h3>
<p class="py-4">This is a modal dialog.</p>
<div class="modal-action">
<form method="dialog">
<button class="btn">Close</button>
</form>
</div>
</div>
</dialog>
Navbar
<div class="navbar bg-base-100">
<div class="flex-1">
<a class="btn btn-ghost text-xl">MyApp</a>
</div>
<div class="flex-none">
<ul class="menu menu-horizontal px-1">
<li><a>Home</a></li>
<li><a>About</a></li>
<li><a>Contact</a></li>
</ul>
</div>
</div>
Dark Mode
Theme Switcher
Built-in theme switcher in js/theme-switcher.js:
Using in Templates
<!-- Theme toggle button -->
<label class="swap swap-rotate">
<input type="checkbox" class="theme-controller" />
<svg class="swap-on"><!-- Sun icon --></svg>
<svg class="swap-off"><!-- Moon icon --></svg>
</label>
Custom Themes
Edit vite.config.js:
Vite Configuration
Default Config
// vite.config.js
import { defineConfig } from 'vite'
export default defineConfig({
root: 'frontend',
build: {
outDir: 'static/dist',
manifest: true,
rollupOptions: {
input: {
main: 'frontend/js/main.js',
style: 'frontend/css/input.css'
}
}
},
server: {
port: 3001,
cors: true
}
})
Custom Configuration
export default defineConfig({
// Add custom plugins
plugins: [vue()],
// Optimize dependencies
optimizeDeps: {
include: ['axios', 'lodash']
},
// Configure proxy
server: {
proxy: {
'/api': 'http://localhost:8000'
}
}
})
Development vs Production
Development Mode
When running uv run myfy run:
- Vite dev server starts automatically
- HMR (Hot Module Replacement) enabled
- Assets served from
http://localhost:3001 - Templates reload on changes
- Source maps included
Production Mode
Build for production:
# Build frontend assets
uv run myfy frontend build
# Run application
MYFY_PROFILE=prod uv run myfy run --no-reload
Production features: - Minified CSS and JavaScript - Tree-shaking for smaller bundles - Asset hashing for cache busting - Gzip compression - Optimized images
Static Files
Serving Static Assets
from myfy.web import route
from starlette.responses import FileResponse
@route.get("/images/{filename}")
async def serve_image(filename: str):
return FileResponse(f"frontend/static/images/{filename}")
URL Helpers
{# In templates #}
<link rel="stylesheet" href="{{ url_for('static', path='dist/style.css') }}">
<script src="{{ url_for('static', path='dist/main.js') }}"></script>
<img src="{{ url_for('static', path='images/logo.png') }}">
Forms and HTMX
Form Handling
from pydantic import BaseModel
class ContactForm(BaseModel):
name: str
email: str
message: str
@route.get("/contact")
async def contact_page():
return render_template("contact.html")
@route.post("/contact")
async def submit_contact(body: ContactForm):
# Process form
return render_template("success.html", name=body.name)
HTMX Integration
<!-- Install HTMX -->
<script src="https://unpkg.com/[email protected]"></script>
<!-- Dynamic content -->
<button
hx-get="/api/data"
hx-target="#result"
class="btn btn-primary"
>
Load Data
</button>
<div id="result"></div>
API Reference
For detailed frontend API documentation, refer to the source code in packages/myfy-frontend/myfy/frontend/.
Key exports:
- FrontendModule - Main module for frontend integration
- render_template() - Function to render Jinja2 templates
Best Practices
Use Template Components
{# ✓ Good - Reusable components #}
{% from "components/button.html" import button %}
{{ button(text="Submit", color="primary") }}
{# ✗ Bad - Inline HTML #}
<button class="btn btn-primary">Submit</button>
Organize Templates
templates/
├── base.html # Base layout
├── components/ # Reusable components
│ ├── navbar.html
│ └── footer.html
├── pages/ # Full pages
│ ├── home.html
│ └── about.html
└── partials/ # Page sections
└── hero.html
Use Tailwind Classes Wisely
<!-- ✓ Good - Use DaisyUI components -->
<button class="btn btn-primary">Click me</button>
<!-- ✗ Bad - Too many utility classes -->
<button class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600">
Click me
</button>
Build for Production
# ✓ Good - Build before deploying
uv run myfy frontend build
# ✗ Bad - Use dev server in production
# Don't do this!
Examples
Full Page Example
# app.py
from myfy.core import Application
from myfy.web import WebModule, route
from myfy.frontend import FrontendModule, render_template
app = Application(auto_discover=False)
app.add_module(WebModule())
app.add_module(FrontendModule())
@route.get("/")
async def home():
return render_template(
"home.html",
title="Welcome",
hero_text="Build amazing apps with myfy"
)
{# templates/home.html #}
{% extends "base.html" %}
{% from "components/navbar.html" import navbar %}
{% block title %}{{ title }}{% endblock %}
{% block content %}
{{ navbar(logo="MyApp") }}
<div class="hero min-h-screen bg-gradient-to-r from-primary to-secondary">
<div class="hero-content text-center text-neutral-content">
<div class="max-w-md">
<h1 class="mb-5 text-5xl font-bold">{{ hero_text }}</h1>
<p class="mb-5">
Combining Python's power with modern frontend tooling.
</p>
<button class="btn btn-accent">Get Started</button>
</div>
</div>
</div>
{% endblock %}
Troubleshooting
Vite Dev Server Not Starting
# Check if Node.js is installed
node --version
# Reinstall dependencies
rm -rf node_modules package-lock.json
npm install
# Manually start Vite
npm run dev
Styles Not Loading
# Rebuild assets
uv run myfy frontend build
# Check Vite config
cat vite.config.js
# Verify CSS import in template
# Should have: <link rel="stylesheet" href="...dist/style.css">
Template Not Found
# Check template path
# Should be: frontend/templates/your-template.html
# Verify template is in the right location
ls frontend/templates/
Next Steps
- Learn Templates: Study Jinja2 template syntax
- Explore DaisyUI: Browse DaisyUI components
- Customize Tailwind: Learn Tailwind customization
- Add Interactivity: Integrate HTMX or Alpine.js
- Deploy: Build and deploy your application