Basic HTML App
Let’s see what are the default headers of a FastHTML app:
from fasthtml.common import *
# hdrs = (MarkdownJS(), HighlightJS(langs=['python', 'javascript', 'html', 'css']),)
= Script(src="https://cdn.tailwindcss.com"),
tlink = Link(rel="stylesheet", href="https://cdn.jsdelivr.net/npm/daisyui@4.11.1/dist/full.min.css")
dlink = (MarkdownJS(), )
m_hdrs = (MarkdownJS(), tlink)
mt_hdrs = (MarkdownJS(), tlink, dlink)
mtd_hdrs = (MarkdownJS(), tlink, dlink, picolink)
mtdp_hdrs
# hdrs = m_hdrs
# hdrs = mt_hdrs
="Default"
hdrs
= FastHTML()
app
= f"""
content Using
{hdrs}
Here are some _markdown_ elements.
- This is a list item
- This is another list item
- And this is a third list item
**Fenced code blocks work here.**
"""
# @rt('/')
@app.route("/")
def get(req):
= f"""
code_content
Using
{hdrs}
Here are some _markdown_ code elements.
- This is a list item
- This is another list item
- And this is a third list item
**Fenced code blocks work here.**
"""
return Titled("Markdown rendering example", Div(content,cls="marked"), Div(code_content, cls="marked"))
serve()
Let’s examine the headers:
Default HTML Headers
<!DOCTYPE html>
<html>
<head>
<title>Markdown rendering example</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<script src="https://unpkg.com/htmx.org@next/dist/htmx.min.js"></script>
<script src="https://cdn.jsdelivr.net/gh/answerdotai/surreal@main/surreal.js"></script>
<script src="https://cdn.jsdelivr.net/gh/gnat/css-scope-inline@main/script.js"></script>
<style>
.htmx-indicator{opacity:0;}
.htmx-request .htmx-indicator{opacity:1; transition: opacity 200ms ease-in;}
</style>
</head>
<body>
<main class="container">
<h1>Markdown rendering example</h1>
<div class="marked">
" Using Default Here are some _markdown_ elements. - This is a list item - This is another list item - And this is a third list item **Fenced code blocks work here.** "</div>
<div class="marked">
" Using Default Here are some _markdown_ code elements. - This is a list item - This is another list item - And this is a third list item **Fenced code blocks work here.** "</div>
</main>
</body>
</html>
Here is what they mean:
<!DOCTYPE html>
:- This declaration defines the document type and version of HTML. It tells the browser that this is an HTML5 document.
<html>
:- The root element of the HTML document. All other elements are nested inside this.
<head>
:- Contains meta-information about the document, such as the title, character set, and links to external resources (like CSS and JavaScript).
<title>
: Sets the title of the webpage, which appears in the browser tab.<meta charset="utf-8">
: Specifies the character encoding for the document, ensuring it can display a wide range of characters correctly.<meta name="viewport">
: This ensures the webpage scales correctly on different devices, especially mobile.<script src="...">
: Links to external JavaScript files.- htmx.js: Enables dynamic interactions and content updates using simple HTML attributes, reducing the need for extensive JavaScript code.
- surreal.js: Provides a concise and efficient API for DOM manipulation, event handling, and animations, simplifying frontend development tasks.
- css-scope-inline.js: Facilitates scoped and modular styling, ensuring CSS rules apply only to designated elements, improving style management and preventing conflicts.
<style>
:- Contains CSS rules that style the document. Here, it controls the visibility and opacity transitions of certain elements related to
htmx
(though this is JavaScript-related).
- Contains CSS rules that style the document. Here, it controls the visibility and opacity transitions of certain elements related to
<body>
:- The body contains all the content of the webpage that is visible to users. This is where the content you created in your Python script is displayed.
<main class="container">
:- A semantic HTML5 element that typically contains the main content of the document.
class="container"
: A CSS class that might apply specific styling (like margins or padding) to the main content area.
<h1>
:- The main heading of the page, displaying “Markdown rendering example”.
<div class="marked">
:- A
div
is a generic container element that groups other elements together. Here, the content is wrapped in two separatediv
elements. class="marked"
: This class doesn’t inherently affect the display on its own but could be used to apply styles or be selected by scripts for further processing.- Content Inside the
div
:- The content inside the
div
is the text you provided in yourcontent
andcode_content
variables in the Python script. However, because no Markdown processing is applied (as you’re using the “Default” setting), the content is displayed as plain text, including the Markdown syntax (like**
for bold and-
for lists).
- The content inside the
- A
htmx.js
What is htmx.js?
htmx.js is a lightweight JavaScript library that enables you to add dynamic and interactive behaviors to your web pages using HTML attributes. It simplifies making HTTP requests and updating parts of your page without writing explicit JavaScript code, promoting a more declarative approach to web development.
What does it do?
htmx allows you to perform actions like fetching content, submitting forms, and handling WebSocket communications directly through HTML attributes. It supports various HTTP methods and can replace, append, or prepend content in specified page elements seamlessly.
Concrete Usage Examples
1. Loading Content with hx-get
Scenario: You want to load additional content into a div when a button is clicked.
HTML Structure:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>surreal.js Example</title>
<script src="https://cdn.jsdelivr.net/gh/answerdotai/surreal@main/surreal.js"></script>
</head>
<body>
<button>Original Button 1</button>
<button>Original Button 2</button>
<script>
// Apply initial styles and text
any('button')
.styles({
'background-color': 'blue',
'color': 'white',
'padding': '10px 20px',
'border': 'none',
'border-radius': '5px'
;
})
// Add click event listener to all buttons
any('button').on('click', function() {
this.text('Clicked!');
this.styles({
'background-color': 'green',
'color': 'white'
;
});
})</script>
</body>
</html>
Explanation: - hx-get="/load-more-content"
: When the button is clicked, htmx makes a GET request to /load-more-content
. - hx-target="#content"
: Specifies that the response should be inserted into the element with the id content
. - hx-swap="beforeend"
: Determines that the new content should be appended to the existing content inside #content
.
Server Response (/load-more-content
):
<p>This is additional content loaded via htmx.</p>
Result: When the button is clicked, the new paragraph is fetched from the server and appended to the existing content without a full page reload.
2. Submitting Forms with hx-post
Scenario: You have a comment form that should submit data asynchronously and display the new comment without reloading the page.
HTML Structure:
<form
hx-post="/submit-comment"
hx-target="#comments"
hx-swap="afterbegin"
hx-trigger="submit">
<textarea name="comment" required></textarea>
<button type="submit">Post Comment</button>
</form>
<div id="comments">
<!-- Existing comments here -->
</div>
Explanation: - hx-post="/submit-comment"
: On form submission, htmx sends a POST request to /submit-comment
with form data. - hx-target="#comments"
: Specifies that the server’s response should be inserted into the #comments
div. - hx-swap="afterbegin"
: Inserts the new comment at the beginning of the comments list. - hx-trigger="submit"
: Defines that the request should be triggered upon form submission.
Server Response (/submit-comment
):
<div class="comment">
<p>User's comment content here.</p>
</div>
Result: After submitting the form, the new comment appears instantly at the top of the comments section without reloading the page.
3. Conditional Content Loading with hx-trigger
Scenario: Load user details when a user hovers over a username.
HTML Structure:
<span
hx-get="/user-details/123"
hx-target="#user-info"
hx-trigger="mouseover">
John Doe</span>
<div id="user-info"></div>
Explanation: - hx-get="/user-details/123"
: Fetches user details from the specified endpoint. - hx-target="#user-info"
: Loads the fetched content into the #user-info
div. - hx-trigger="mouseover"
: The request is triggered when the user hovers over the span containing the username.
Server Response (/user-details/123
):
<div class="user-details">
<p>Name: John Doe</p>
<p>Email: john.doe@example.com</p>
<p>Location: New York, USA</p>
</div>
Result: Hovering over “John Doe” displays additional user information dynamically.
Including htmx.js simplifies adding interactive features to your webpage without writing extensive JavaScript code. It enhances user experience by enabling asynchronous content loading, form submissions, and dynamic updates, leading to faster and more responsive web applications. The declarative nature of htmx makes your code more readable and maintainable.
surreal.js
What is surreal.js?
surreal.js is a minimalistic JavaScript library designed for easy and fluent manipulation of DOM elements. It provides a simple, chainable API similar to jQuery but with a much smaller footprint, making common tasks like selecting elements, adding/removing classes, handling events, and manipulating styles straightforward and concise.
What does it do?
surreal.js offers utility functions to interact with the DOM efficiently. It allows you to select elements using CSS selectors, modify their classes and styles, attach event listeners, and perform animations effortlessly.
Concrete Usage Examples
1. Selecting and Modifying Elements
Scenario: Change the text and style of all buttons on the page.
HTML Structure:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>surreal.js Example</title>
<script src="https://cdn.jsdelivr.net/gh/answerdotai/surreal@main/surreal.js"></script>
</head>
<body>
<button>Original Button 1</button>
<button>Original Button 2</button>
<script>
// Select all button elements and modify them
any('button')
.text('Updated Button')
.styles({
'background-color': 'blue',
'color': 'white',
'padding': '10px 20px',
'border': 'none',
'border-radius': '5px'
;
})</script>
</body>
</html>
Explanation: - any('button')
: Selects all <button>
elements on the page. - .text('Updated Button')
: Sets the text content of each button to “Updated Button”. - .styles({...})
: Applies multiple CSS styles to each button, changing their appearance.
Result: Both buttons on the page now display “Updated Button” and have updated styles applied, giving them a consistent and styled look.
2. Handling Events
Scenario: Display an alert when a specific div is clicked.
HTML Structure:
<div id="alert-box" style="width:200px; height:100px; background-color:lightgrey; display:flex; align-items:center; justify-content:center; cursor:pointer;">
Click Me</div>
<script>
// Add a click event listener to the div
me('#alert-box').on('click', () => {
alert('Div clicked!');
;
})</script>
Explanation: - me('#alert-box')
: Selects the element with id alert-box
. - .on('click', () => { ... })
: Attaches a click event listener that triggers an alert when the div is clicked.
Result: Clicking on the “Click Me” box displays an alert saying “Div clicked!”.
3. Toggling Classes for Interaction
Scenario: Toggle a ‘dark-mode’ class on the body when a button is clicked.
HTML Structure:
<button id="toggle-dark-mode">Toggle Dark Mode</button>
<style>
.dark-mode {
background-color: #121212;
color: #ffffff;
}</style>
<script>
me('#toggle-dark-mode').on('click', () => {
me('body').classToggle('dark-mode');
;
})</script>
Explanation: - me('#toggle-dark-mode')
: Selects the toggle button. - .on('click', () => { ... })
: Attaches a click event that toggles the ‘dark-mode’ class on the body. - me('body').classToggle('dark-mode')
: Adds or removes the ‘dark-mode’ class from the body, switching between light and dark themes.
Result: Clicking the button switches the webpage between light and dark modes by toggling appropriate styles.
4. Fading Elements In and Out
Scenario: Fade out an image when a user clicks on it.
HTML Structure:
<img id="fade-image" src="image.jpg" alt="Sample Image" style="width:300px;">
<script>
me('#fade-image').on('click', () => {
me('#fade-image').fadeOut(1000); // fades out over 1 second
;
})</script>
Explanation: - me('#fade-image')
: Selects the image element. - .on('click', () => { ... })
: Attaches a click event listener. - .fadeOut(1000)
: Gradually reduces the opacity of the image over 1000 milliseconds (1 second) until it’s fully hidden.
Result: Clicking on the image smoothly fades it out, providing a pleasant visual effect.
5. Creating and Appending New Elements
Scenario: Dynamically create and add a new list item to an existing list.
HTML Structure:
<ul id="item-list">
<li>Item 1</li>
<li>Item 2</li>
</ul>
<button id="add-item">Add Item</button>
<script>
me('#add-item').on('click', () => {
const newItem = createElement('li').text('New Item');
me('#item-list').append(newItem);
;
})</script>
Explanation: - me('#add-item')
: Selects the add button. - .on('click', () => { ... })
: Adds a click event that creates and appends a new list item. - createElement('li').text('New Item')
: Creates a new <li>
element and sets its text content. - me('#item-list').append(newItem)
: Appends the new item to the existing list.
Result: Clicking the “Add Item” button adds a new item to the list dynamically.
Including surreal.js in your webpage simplifies DOM manipulation tasks, allowing you to write cleaner and more concise code. It facilitates rapid development by providing easy-to-use methods for common operations like selecting elements, handling events, and updating styles without the overhead of larger libraries. This leads to improved performance and maintainability of your web application.
css-scope-inline.js
What is css-scope-inline.js?
css-scope-inline.js is a utility script that allows you to apply CSS styles scoped to specific elements or components directly inline. This means the styles you define will only affect the intended elements, preventing unintended style overrides and conflicts elsewhere on the page.
What does it do?
This script processes inline CSS and ensures that styles are applied only within a specified scope. It can dynamically generate unique identifiers for elements and apply styles accordingly, promoting modular and maintainable CSS practices.
Concrete Usage Examples
1. Applying Scoped Styles to a Component
Scenario: You have multiple card components on your page, and you want to apply specific styles to one of them without affecting the others.
HTML Structure:
<div class="card" id="special-card">
<h2>Special Card</h2>
<p>This card has unique styles.</p>
</div>
<div class="card">
<h2>Regular Card</h2>
<p>This card uses default styles.</p>
</div>
<script src="https://cdn.jsdelivr.net/gh/gnat/css-scope-inline@main/script.js"></script>
<script>
cssScopeInline('#special-card', `
.card {
background-color: #ffeeba;
border: 2px solid #ffc107;
padding: 20px;
border-radius: 10px;
}
.card h2 {
color: #856404;
}
.card p {
color: #704c00;
}
`);
</script>
Explanation: - cssScopeInline('#special-card',
…);
: Applies the enclosed CSS styles exclusively to the element with id special-card
. - Defined styles: Customize the background, border, padding, and text colors specifically for this card. - The other .card
elements on the page remain unaffected by these styles.
Result: The “Special Card” displays with unique styling, while other cards retain their default appearance.
2. Dynamic Styling Based on User Interaction
Scenario: Change the style of a section when a user interacts with it, ensuring styles don’t leak to other sections.
HTML Structure:
<div class="interactive-section" id="section-1">
<p>Click me to change my style!</p>
</div>
<div class="interactive-section" id="section-2">
<p>I'm another section.</p>
</div>
<script src="https://cdn.jsdelivr.net/gh/gnat/css-scope-inline@main/script.js"></script>
<script>
me('#section-1').on('click', () => {
cssScopeInline('#section-1', `
.interactive-section {
background-color: #d1ecf1;
color: #0c5460;
padding: 15px;
border-left: 5px solid #0c5460;
}
.interactive-section p {
font-weight: bold;
}
`);
;
})</script>
Explanation: - me('#section-1').on('click', () => { ... });
: Adds a click event to section-1
. - cssScopeInline('#section-1',
…);
: Applies new styles to section-1
upon click. - The styles include background color change, text color, padding, and font weight adjustments. - Isolation: section-2
remains unaffected despite sharing the same class.
Result: Clicking on the first section updates its styles dynamically without impacting the second section.
3. Theming Specific Components
Scenario: Apply a dark theme to a modal dialog without altering the global styles.
HTML Structure:
<div class="modal" id="dark-modal">
<h3>Dark Themed Modal</h3>
<p>This modal uses a dark theme.</p>
<button>Close</button>
</div>
<script src="https://cdn.jsdelivr.net/gh/gnat/css-scope-inline@main/script.js"></script>
<script>
cssScopeInline('#dark-modal', `
.modal {
background-color: #343a40;
color: #ffffff;
padding: 30px;
border-radius: 8px;
width: 400px;
margin: 100px auto;
box-shadow: 0 5px 15px rgba(0,0,0,0.5);
}
.modal button {
background-color: #6c757d;
color: #ffffff;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
}
.modal button:hover {
background-color: #5a6268;
}
`);
</script>
Explanation: - cssScopeInline('#dark-modal',
…);
: Applies dark theme styles exclusively to the dark-modal
component. - Styles include dark background, white text, styled button, and shadow effects. - Other modals or components with the .modal
class are not affected by these styles.
Result: The modal dialog appears with a sleek dark theme, enhancing user experience without interfering with other components’ styles.
Including css-scope-inline.js helps maintain clean and modular CSS by ensuring styles are applied only where intended. It prevents style conflicts and unintended overrides, especially in large applications or when integrating third-party components. This approach enhances maintainability and scalability of your stylesheets, leading to more predictable and consistent styling across your application.
The <style>
tag in the provided HTML code contains CSS rules that define the appearance and behavior of elements on the page. In this case, the CSS rules are specifically targeting elements related to htmx
, a JavaScript library for adding dynamic behavior to web pages.
Default CSS style rules
<style>
.htmx-indicator{opacity:0;}
.htmx-request .htmx-indicator{opacity:1; transition: opacity 200ms ease-in;}
</style>
Rule 1: .htmx-indicator{opacity:0;}
- Selector:
.htmx-indicator
- Property:
opacity
- Value:
0
This rule sets the opacity of elements with the class htmx-indicator
to 0
, making them fully transparent (invisible).
Rule 2: .htmx-request .htmx-indicator{opacity:1; transition: opacity 200ms ease-in;}
- Selector:
.htmx-request .htmx-indicator
- Properties:
opacity: 1;
transition: opacity 200ms ease-in;
This rule applies to elements with the class htmx-indicator
that are descendants of elements with the class htmx-request
. It sets their opacity to 1
(fully visible) and specifies a transition effect for the opacity change, which will take 200ms
and use an ease-in
timing function.
Detailed Examples
Example 1: Basic Usage
Imagine you have a button that, when clicked, triggers an htmx
request. You want to show a loading indicator during the request.
HTML Structure:
<button hx-get="/load-data" hx-target="#result" class="htmx-request">
Load Data<span class="htmx-indicator">Loading...</span>
</button>
<div id="result"></div>
Explanation: - The button has the htmx-request
class and an htmx
attribute (hx-get
) that triggers a request to /load-data
. - The span
inside the button has the htmx-indicator
class.
Behavior: - Initially, the htmx-indicator
span is invisible due to opacity: 0;
. - When the button is clicked and the request is in progress, the htmx-request
class is added to the button, making the htmx-indicator
span visible (opacity: 1;
) with a smooth transition.
Example 2: Custom Loading Indicator
You can customize the loading indicator to be more visually appealing.
HTML Structure:
<button hx-get="/load-data" hx-target="#result" class="htmx-request">
Load Data<span class="htmx-indicator">
<img src="spinner.gif" alt="Loading...">
</span>
</button>
<div id="result"></div>
CSS:
<style>
.htmx-indicator {
opacity: 0;
display: inline-block;
margin-left: 10px;
}.htmx-request .htmx-indicator {
opacity: 1;
transition: opacity 200ms ease-in;
}</style>
Explanation: - The htmx-indicator
span now contains an image (spinner.gif
) to serve as a loading indicator. - The display: inline-block;
and margin-left: 10px;
properties ensure the indicator is properly positioned next to the button text.
Behavior: - The loading spinner is initially invisible. - When the request is in progress, the spinner becomes visible with a smooth transition.
Summary
The <style>
tag in your HTML code is used to define CSS rules that control the visibility and transition effects of elements with the htmx-indicator
class during htmx
requests. This enhances the user experience by providing visual feedback when asynchronous operations are in progress.
How is the default header built
# %% ../nbs/api/00_core.ipynb
class FastHTML(Starlette):
def __init__(self, debug=False, routes=None, middleware=None, exception_handlers=None,
=None, on_shutdown=None, lifespan=None, hdrs=None, ftrs=None,
on_startup=None, after=None, ws_hdr=False,
before=True, htmx=True, default_hdrs=True, sess_cls=SessionMiddleware,
surreal=None, session_cookie='session_', max_age=365*24*3600, sess_path='/',
secret_key='lax', sess_https_only=False, sess_domain=None, key_fname='.sesskey',
same_site=None, **bodykw):
htmlkw= map(_list, (middleware,before,after))
middleware,before,after = get_key(secret_key, key_fname)
secret_key if sess_cls:
= Middleware(sess_cls, secret_key=secret_key,session_cookie=session_cookie,
sess =max_age, path=sess_path, same_site=same_site,
max_age=sess_https_only, domain=sess_domain)
https_only
middleware.append(sess)= listify(hdrs),listify(ftrs)
hdrs,ftrs = htmlkw or {}
htmlkw if default_hdrs:
if surreal: hdrs = [surrsrc,scopesrc] + hdrs
if ws_hdr: hdrs = [htmxwsscr] + hdrs
if htmx: hdrs = [htmxscr] + hdrs
= [charset, viewport] + hdrs
hdrs = {k:_wrap_ex(v, hdrs, ftrs, htmlkw, bodykw) for k,v in (exception_handlers or {}).items()}
excs super().__init__(debug, routes, middleware, excs, on_startup, on_shutdown, lifespan=lifespan)
self.router = RouterX(routes, on_startup=on_startup, on_shutdown=on_shutdown, lifespan=lifespan,
=hdrs, ftrs=ftrs, before=before, after=after, htmlkw=htmlkw, **bodykw) hdrs
When default_hdrs=True
, and surreal
is true it will add the Javascript for surreal and scoping.
<script src="https://unpkg.com/htmx.org@next/dist/htmx.min.js"></script>
<script src="https://cdn.jsdelivr.net/gh/answerdotai/surreal@main/surreal.js"></script>
<script src="https://cdn.jsdelivr.net/gh/gnat/css-scope-inline@main/script.js"></script>
<style>
.htmx-indicator{opacity:0;}
.htmx-request .htmx-indicator{opacity:1; transition: opacity 200ms ease-in;}
</style>
Add MarkdownJS
Let’s add markdown parser to the headers
...= (MarkdownJS(), )
m_hdrs = m_hdrs
hdrs = FastHTML(hdrs=hdrs)
app ...
How the Markdown Rendering Script Works
Script Inclusion: The script is included in the HTML document within a
<script type="module">
tag. This allows the use of ES6 module syntax for importing themarked
library and a customproc_htmx
function.<script type="module"> import { marked } from "https://cdn.jsdelivr.net/npm/marked/lib/marked.esm.js"; import { proc_htmx } from "https://cdn.jsdelivr.net/gh/answerdotai/fasthtml-js/fasthtml.js"; proc_htmx('.marked', e => e.innerHTML = marked.parse(e.textContent)); </script>
Importing Libraries:
marked
: A popular library for converting Markdown to HTML.proc_htmx
: A custom function from thefasthtml
library that processes elements matching a given selector.
Processing Elements: The
proc_htmx
function is called with two arguments:- A CSS selector (
.marked
) to target elements with the classmarked
. - A callback function that sets the
innerHTML
of each matched element to the result ofmarked.parse(e.textContent)
, which converts the Markdown text content to HTML.
- A CSS selector (
Detailed Examples
Example 1: Basic Markdown Conversion
HTML Structure:
<div class="marked">
Here are some _markdown_ elements.
- This is a list item
- This is another list item
- And this is a third list item
**Fenced code blocks work here.**</div>
Rendered HTML:
<div class="marked">
<p>Here are some <em>markdown</em> elements.</p>
<ul>
<li>This is a list item</li>
<li>This is another list item</li>
<li>And this is a third list item</li>
</ul>
<p><strong>Fenced code blocks work here.</strong></p>
</div>
Explanation: - The div
with the class marked
contains Markdown text. - The script processes this div
, converting the Markdown to HTML using the marked
library. - The resulting HTML is set as the innerHTML
of the div
, rendering the Markdown as formatted HTML.
Example 2: Code Block Conversion
HTML Structure:
<div class="marked">
```python
def hello_world():
print("Hello, world!")
```</div>
Rendered HTML:
<div class="marked">
<pre><code class="language-python">def hello_world():
print("Hello, world!")</code></pre>
</div>
Explanation: - The div
with the class marked
contains a fenced code block in Markdown. - The script processes this div
, converting the Markdown code block to HTML using the marked
library. - The resulting HTML includes <pre>
and <code>
tags with appropriate classes for syntax highlighting.
Replace Markdown JS with Tailwind CSS
Removing MarkdownJS
- Markdown Not Rendered:
- Without the Markdown rendering script, the Markdown syntax (e.g.,
**bold**
,_italic_
,- list item
) is not converted to HTML. Instead, it is displayed as plain text. - This means that elements like lists, bold text, and italic text are not rendered correctly.
- Without the Markdown rendering script, the Markdown syntax (e.g.,
- Raw Markdown Displayed:
- The content inside the
div
with the classmarked
is shown as raw Markdown text, which includes the Markdown syntax characters.
- The content inside the
Adding Tailwind CSS
- Tailwind CSS Utility Classes:
- Tailwind CSS is a utility-first CSS framework that provides a set of classes to control the layout, spacing, colors, typography, and other aspects of your HTML elements.
- When you add Tailwind CSS, it doesn’t automatically style your content unless you use its utility classes.
- Default Styles:
- Tailwind CSS includes some base styles that normalize the appearance of HTML elements across different browsers. This can change the default appearance of elements like headings, paragraphs, and lists.
- Tailwind CSS’s base reset removes default margins and paddings, making it no space between the line start and the window border. To add space, you can use Tailwind CSS utility classes to add padding or margin to your elements.
- However, without specific Tailwind utility classes applied to your elements, the content will not look significantly different from the default browser styles.
Why It Looks Different
- Lack of Markdown Conversion:
- The primary reason your content looks different is that the Markdown syntax is not being converted to HTML. This results in the raw Markdown text being displayed, which is not styled or formatted as intended.
- Tailwind CSS Base Styles:
- Tailwind CSS applies some base styles that might slightly alter the appearance of your text, such as font sizes, line heights, and margins. However, these changes are usually subtle and not the main reason for the drastic difference in appearance.
Example Comparison
With Markdown JS (Rendered Markdown)
HTML Structure:
<div class="marked">
Here are some _markdown_ elements.
- This is a list item
- This is another list item
- And this is a third list item
**Fenced code blocks work here.**</div>
Rendered HTML:
<div class="marked">
<p>Here are some <em>markdown</em> elements.</p>
<ul>
<li>This is a list item</li>
<li>This is another list item</li>
<li>And this is a third list item</li>
</ul>
<p><strong>Fenced code blocks work here.</strong></p>
</div>
Without Markdown JS (Raw Markdown)
HTML Structure:
<div class="marked">
Here are some _markdown_ elements.
- This is a list item
- This is another list item
- And this is a third list item
**Fenced code blocks work here.**</div>
Displayed Content:
Here are some _markdown_ elements.
- This is a list item
- This is another list item
- And this is a third list item
**Fenced code blocks work here.**
Adding Tailwind CSS
HTML Structure with Tailwind CSS:
<div class="marked">
Here are some _markdown_ elements.
- This is a list item
- This is another list item
- And this is a third list item
**Fenced code blocks work here.**</div>
Tailwind CSS Link:
<link href="https://cdn.tailwindcss.com" rel="stylesheet">
Result: - The content will still display the raw Markdown text. - Tailwind CSS base styles might slightly alter the appearance, but the Markdown syntax will not be converted to HTML.
Conclusion
To achieve the desired appearance, you need both the Markdown rendering script to convert Markdown syntax to HTML and Tailwind CSS to style the HTML elements. Removing the Markdown rendering script results in raw Markdown text being displayed, while adding Tailwind CSS without applying its utility classes does not significantly change the appearance of the content.
Use DaisyUI instead of Taiwind
… d_hdrs = (dlink, ) hdrs = d_hdrs app = FastHTML(hdrs=hdrs) …
When you change from Tailwind CSS to DaisyUI, you are essentially switching from a utility-first CSS framework to a component-based CSS framework built on top of Tailwind CSS. DaisyUI provides pre-designed components and utility classes that make it easier to build user interfaces quickly.
What Happens When You Use DaisyUI
- Component-Based Styling:
DaisyUI provides a set of pre-designed components like buttons, cards, forms, and more. These components come with predefined styles that you can use directly in your HTML.
Example:
<button class="btn btn-primary">Primary Button</button>
- Utility Classes:
- DaisyUI still allows you to use Tailwind CSS utility classes, as it is built on top of Tailwind CSS. This means you can combine DaisyUI components with Tailwind’s utility classes for more granular control over your styles.
- Theming:
- DaisyUI supports theming, allowing you to easily switch between different themes or create your own custom themes.
Summary
Switching to DaisyUI provides you with pre-designed components and utility classes that simplify the process of building user interfaces. You can still use Tailwind CSS utility classes alongside DaisyUI components for more control over your styles. The example above demonstrates how to use DaisyUI to style your HTML elements, making it easier to create a visually appealing design.
Using picolink
...= (picolink, )
p_hdrs = p_hdrs
hdrs = FastHTML(hdrs=hdrs)
app ...
Pico.css is designed to provide a clean and minimalistic design with sensible defaults for HTML elements. This means you won’t need to add many classes to achieve a good-looking design.
Directly using Pico.css to style without markdown
HTML Structure:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Styled Content Example</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@latest/css/pico.min.css">
<style>
.htmx-indicator{opacity:0;}
.htmx-request .htmx-indicator{opacity:1; transition: opacity 200ms ease-in;}
</style>
</head>
<body>
<main class="container">
<h1>Styled Content Example</h1>
<article>
<p>Here are some <em>styled</em> elements.</p>
<ul>
<li>This is a list item</li>
<li>This is another list item</li>
<li>And this is a third list item</li>
</ul>
<p><strong>Fenced code blocks work here.</strong></p>
</article>
<article>
<p>Here are some <em>styled</em> code elements.</p>
<ul>
<li>This is a list item</li>
<li>This is another list item</li>
<li>And this is a third list item</li>
</ul>
<p><strong>Fenced code blocks work here.</strong></p>
</article>
</main>
</body>
</html>
Explanation
- Pico.css for Base Styles:
- Container (
<main>
): Thecontainer
class provided by Pico.css centers the content and applies appropriate padding. - Headings (
<h1>
): Styled by Pico.css to have appropriate font sizes and margins. - Paragraphs (
<p>
): Styled by Pico.css to have appropriate margins, font sizes, and line heights. - Emphasis (
<em>
): Styled by Pico.css to italicize text. - Lists (
<ul>
,<li>
): Styled by Pico.css to have appropriate list styles and spacing. - Strong (
<strong>
): Styled by Pico.css to bold text. - Article (
<article>
): Used to semantically group content, and it will inherit Pico.css’s default styles.
- Container (
Result
With this approach, Pico.css provides all the necessary styling for the HTML elements, ensuring a clean and minimalistic design without the need for additional classes. This leverages the strengths of Pico.css to create a visually appealing and well-structured design with minimal effort.
Visual Indicators
To help you visually distinguish the styles applied by Pico.css, you can use browser developer tools:
- Inspect Elements:
- Right-click on an element and select “Inspect” to open the developer tools.
- Look at the “Styles” panel to see which CSS rules are applied to the element.
- Pico.css styles will typically be applied without any class selectors, as they target HTML elements directly.
Pico and Tailwind together
...= (picolink, tlink, )
pt_hdrs = tp_hdrs
hdrs = FastHTML(hdrs=hdrs)
app ...
...= (tlink, picolink, )
tp_hdrs = tp_hdrs
hdrs = FastHTML(hdrs=hdrs)
app ...
Tailwind CSS provides utility classes that you explicitly add to elements to apply specific styles. These classes are prefixed with utility names like p-4
, bg-gray-100
, rounded-lg
, etc.
Pico.css provides default styles for HTML elements without the need for additional classes. These styles are applied automatically to elements like paragraphs, lists, headings, etc.
Directly using Pico.css for Base Styles and Tailwind CSS for Customization
HTML Structure:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Styled Content Example</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@latest/css/pico.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css">
<style>
.htmx-indicator{opacity:0;}
.htmx-request .htmx-indicator{opacity:1; transition: opacity 200ms ease-in;}
</style>
</head>
<body>
<main class="container mx-auto p-4">
<h1 class="text-3xl font-bold mb-4">Styled Content Example</h1>
<article class="p-4 bg-gray-100 rounded-lg shadow-md">
<p>Here are some <em>styled</em> elements.</p>
<ul>
<li>This is a list item</li>
<li>This is another list item</li>
<li>And this is a third list item</li>
</ul>
<p><strong>Fenced code blocks work here.</strong></p>
</article>
<article class="p-4 bg-gray-100 rounded-lg shadow-md mt-4">
<p>Here are some <em>styled</em> code elements.</p>
<ul>
<li>This is a list item</li>
<li>This is another list item</li>
<li>And this is a third list item</li>
</ul>
<p><strong>Fenced code blocks work here.</strong></p>
</article>
</main>
</body>
</html>
Explanation
- Pico.css for Base Styles:
- Paragraphs (
<p>
): Styled by Pico.css to have appropriate margins, font sizes, and line heights. - Emphasis (
<em>
): Styled by Pico.css to italicize text. - Lists (
<ul>
,<li>
): Styled by Pico.css to have appropriate list styles and spacing. - Strong (
<strong>
): Styled by Pico.css to bold text.
- Paragraphs (
- Tailwind CSS for Customization:
- Container (
<main>
): Uses Tailwind CSS utility classescontainer
,mx-auto
, andp-4
for layout and padding. - Heading (
<h1>
): Uses Tailwind CSS utility classestext-3xl
,font-bold
, andmb-4
for font size, weight, and margin-bottom. - Article (
<article>
): Uses Tailwind CSS utility classesp-4
,bg-gray-100
,rounded-lg
,shadow-md
, andmt-4
for padding, background color, rounded corners, shadow, and margin-top.
- Container (
Visual Indicators
To help you visually distinguish between the styles applied by Pico.css and Tailwind CSS, you can use browser developer tools:
- Inspect Elements:
- Right-click on an element and select “Inspect” to open the developer tools.
- Look at the “Styles” panel to see which CSS rules are applied to the element.
- Pico.css styles will typically be applied without any class selectors, as they target HTML elements directly.
- Tailwind CSS styles will be applied through utility classes, which you can see in the class attribute of the element.
- Class Names:
- Tailwind CSS utility classes are prefixed with specific names like
p-4
,bg-gray-100
,rounded-lg
, etc. - Pico.css does not require additional class names for its default styles, so elements styled by Pico.css will not have these utility class names.
- Tailwind CSS utility classes are prefixed with specific names like
Bring back Markdown JS with pico
...= (MarkdownJS(), picolink, )
mp_hdrs = mp_hdrs
hdrs = FastHTML(hdrs=hdrs)
app ...
This is already so much better compare to the barebone MarkdownJS version.
Let’s bring in Daisy Chat components
from fasthtml.common import *
# First we instantiate our app, in this case we remove the
# default headers to reduce the size of the output.
= Script(src="https://cdn.tailwindcss.com"),
tlink = Link(rel="stylesheet", href="https://cdn.jsdelivr.net/npm/daisyui@4.11.1/dist/full.min.css")
dlink = (picolink, )
p_hdrs = (dlink, )
d_hdrs = (MarkdownJS(), )
m_hdrs = (picolink, dlink, )
pd_hdrs = FastHTML(hdrs=d_hdrs)
app
= [
messages "role":"user", "content":"Hello"},
{"role":"assistant", "content":"Hi, how can I assist you"}
{
]
def ChatMessage(msg):
return Div(
'role'], cls="chat-header"),
Div(msg['content'], cls=f"chat-bubble chat-bubble-{'primary' if msg['role'] == 'user' else 'secondary'}"),
Div(msg[=f"chat chat-{'end' if msg['role'] == 'user' else 'start'}"
cls
)
# Usage example
@app.route("/")
def get():
= [ChatMessage(msg) for msg in messages]
chat_messages = Div(*chat_messages, cls="chat-box", id="chatlist")
chatbox return Titled("FastHTML is awesome", chatbox)
serve()
# # Setting up the Starlette test client
# from starlette.testclient import TestClient
# client = TestClient(app)
# content_html = client.get("/").text
# display(HTML(content_html))
Daisy UI only
...= (dlink, )
d_hdrs = FastHTML(hdrs=d_hdrs)
app ...
Daisy + Pico
...= (picolink, dlink, )
pd_hdrs = FastHTML(hdrs=pd_hdrs)
app ...
We got better margin/padding support on the overall UI.