Learn to create reusable MintyFlask components in 5 minutes

Component Development Quick Start

What You Need to Know

MintyFlask components combine semantic CSS classes with Jinja2 macros to create reusable, configurable UI elements. Think of them as building blocks you can use across your site.

Two types of components:

  1. CSS Components - Semantic classes (.card-base, .btn-base)
  2. Macro Components - Jinja2 templates with logic

Most of the time, you'll use existing CSS components and occasionally create macros to orchestrate them.

The Core Component Pattern

CSS Component (Foundation)

/* Semantic classes in @layer components */
@layer components {
  .card-base {
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-lg);
    padding: var(--spacing-lg);
  }

  .card-title {
    font-size: var(--text-xl);
    font-weight: 600;
    margin-bottom: var(--spacing-sm);
  }
}

Using in Templates

<!-- Direct usage -->
<div class="card-base">
  <h3 class="card-title">My Card</h3>
  <p class="card-description">Card content here...</p>
</div>

Macro Component (Optional Layer)

{# macros.html #}
{% macro simple_card(title, content) %}
<div class="card-base">
  <h3 class="card-title">{{ title }}</h3>
  <p class="card-description">{{ content }}</p>
</div>
{% endmacro %}
{# In your template #}
{% from "macros.html" import simple_card %}

{{ simple_card("My Card", "Card content here...") }}

Using Existing CSS Components

MintyFlask provides CSS component classes you can use directly.

Cards

<!-- Basic card -->
<div class="card-base card-intent-content card-size-md">
  <div class="card-header">
    <h3 class="card-title">Card Title</h3>
  </div>
  <div class="card-body">
    <p class="card-description">Card content...</p>
  </div>
  <div class="card-footer">
    <div class="card-actions">
      <button class="btn-base btn-intent-primary btn-size-sm">Action</button>
    </div>
  </div>
</div>

Buttons

<!-- Primary button -->
<button class="btn-base btn-intent-primary btn-size-md">Click Me</button>

<!-- Secondary button with icon -->
<button class="btn-base btn-intent-secondary btn-size-sm btn-icon-left">
  <svg><!-- icon --></svg>
  With Icon
</button>

Badges

<!-- Info badge -->
<span class="badge-base badge-intent-info badge-size-sm"> New </span>

<!-- Warning badge -->
<span class="badge-base badge-intent-warning badge-size-md"> Beta </span>

Component Naming Pattern

All components follow a consistent pattern:

{component}-{element}--{modifier}

Examples:
.card-base              # Base component
.card-title             # Component element
.card-intent-primary    # Intent modifier
.card-size-lg           # Size modifier

The pattern:

  • Base class - .card-base, .btn-base, .badge-base
  • Element classes - .card-title, .card-body, .btn-icon
  • Intent modifiers - .card-intent-primary, .btn-intent-secondary
  • Size modifiers - .card-size-sm, .btn-size-lg
  • State modifiers - .btn-style-ghost, .card-clickable

Creating Your First Macro

Let's create a reusable alert component.

Step 1: Use Existing CSS Components

<!-- This works, but repetitive -->
<div class="card-base card-intent-warning card-size-md">
  <div class="card-body"><strong>Warning:</strong> This is important!</div>
</div>

Step 2: Create a Macro

{# components/alerts.html #}
{% macro alert(message, type="info") %}
<div class="card-base card-intent-{{ type }} card-size-md" role="alert">
  <div class="card-body">
    {% if type == "warning" %}
      <strong>Warning:</strong>
    {% elif type == "error" %}
      <strong>Error:</strong>
    {% elif type == "success" %}
      <strong>Success:</strong>
    {% endif %}
    {{ message }}
  </div>
</div>
{% endmacro %}

Step 3: Use Your Macro

{# In your template #}
{% from "components/alerts.html" import alert %}

{{ alert("This is important!", type="warning") }}
{{ alert("Everything worked!", type="success") }}
{{ alert("Something went wrong.", type="error") }}

Creating Mixin Components

When building a mixin (like a blog feature), create CSS components:

/* mixins/blog/static/css/blog.css */

@layer components {
  /* Post card base */
  .blog-post-card {
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-lg);
    padding: var(--spacing-lg);
  }

  /* Post card elements */
  .blog-post-title {
    font-size: var(--text-xl);
    font-weight: 600;
    margin-bottom: var(--spacing-sm);
  }

  .blog-post-excerpt {
    color: var(--color-text-secondary);
    margin-bottom: var(--spacing-md);
  }

  /* Post card meta */
  .blog-post-meta {
    display: flex;
    gap: var(--spacing-md);
    font-size: var(--text-sm);
    color: var(--color-text-tertiary);
  }
}

Template usage:

<article class="blog-post-card">
  <h2 class="blog-post-title">
    <a href="/post/slug">Post Title</a>
  </h2>
  <p class="blog-post-excerpt">Brief summary of the post...</p>
  <div class="blog-post-meta">
    <time>November 25, 2024</time>
    <span>5 min read</span>
  </div>
</article>

Component Variants

Use modifier classes for variations:

<!-- Size variants -->
<button class="btn-base btn-intent-primary btn-size-sm">Small</button>
<button class="btn-base btn-intent-primary btn-size-md">Medium</button>
<button class="btn-base btn-intent-primary btn-size-lg">Large</button>

<!-- Intent variants -->
<div class="card-base card-intent-content">Content Card</div>
<div class="card-base card-intent-feature">Feature Card</div>
<div class="card-base card-intent-info">Info Card</div>

<!-- Combined variants -->
<button class="btn-base btn-intent-secondary btn-size-lg btn-style-ghost">
  Ghost Button
</button>

Quick Component Examples

Hero Section

<div class="hero-section">
  <h1 class="text-3xl font-bold mb-4">Welcome</h1>
  <p class="text-lg text-slate-600 dark:text-stone-400 mb-6">
    Get started with MintyFlask
  </p>
  <button class="btn-base btn-intent-primary btn-size-lg btn-icon-right">
    Get Started
    <svg class="w-5 h-5"><!-- arrow icon --></svg>
  </button>
</div>

Navigation Links

<nav class="flex gap-4">
  <a
    href="/about"
    class="btn-base btn-intent-secondary btn-size-sm btn-style-ghost"
  >
    About
  </a>
  <a
    href="/contact"
    class="btn-base btn-intent-secondary btn-size-sm btn-style-ghost"
  >
    Contact
  </a>
  <a href="/blog" class="btn-base btn-intent-primary btn-size-sm"> Blog </a>
</nav>

Info Box

<div class="card-base card-intent-info card-size-md">
  <div class="card-header">
    <h3 class="card-title">Did You Know?</h3>
  </div>
  <div class="card-body">
    <p class="card-description">
      MintyFlask uses semantic CSS components for consistency.
    </p>
  </div>
</div>

Dark Mode Support

All components automatically support dark mode through CSS variables:

@layer components {
  .blog-post-card {
    background: var(--color-surface);
    border-color: var(--color-border);
    color: var(--color-text-primary);
  }

  /* Variables change automatically in dark mode */
  /* No extra code needed! */
}

Just ensure you use CSS custom properties instead of hardcoded colours.

Common Patterns

Pattern 1: Direct CSS Usage

<!-- Best for simple, one-off components -->
<div class="card-base card-intent-content card-size-md">
  <div class="card-body">
    <h3 class="card-title">Simple Card</h3>
  </div>
</div>

Pattern 2: Macro Wrapper

<!-- Best for repetitive components with logic -->
{% macro feature_card(title, description, icon) %}
<div class="card-base card-intent-feature card-size-lg">
  <div class="card-header">
    <div class="text-4xl mb-4">{{ icon }}</div>
    <h3 class="card-title">{{ title }}</h3>
  </div>
  <div class="card-body">
    <p class="card-description">{{ description }}</p>
  </div>
</div>
{% endmacro %}

Pattern 3: Partial Include

<!-- Best for complex, reusable layouts -->
{% for post in posts %} {% include "partials/post-card.html" %} {% endfor %}

When to Create a Macro

Create a macro when:

  • Component is used in multiple places
  • Component has conditional logic
  • Component needs configuration parameters
  • You want consistent structure

Don't create a macro when:

  • Component is used once
  • Simple CSS classes are sufficient
  • No logic or parameters needed

Troubleshooting

Component styles not applying

Check:

  1. Is the CSS file imported in input.css?
  2. Are styles in the correct @layer?
  3. Are you using the right class names?
/* Make sure CSS is imported */
@layer components {
  @import "../../mixins/blog/static/css/blog.css";
}

Macro not found

Check:

  1. Is import path correct?
  2. Did you add with context if using Flask functions?
{# Wrong #}
{% from "alerts.html" import alert %}

{# Right #}
{% from "components/alerts.html" import alert with context %}

Variables not working

Check:

  1. Are you using CSS custom properties?
  2. Are variables defined in :root or .dark?
/* Wrong */
.card {
  color: #333;
}

/* Right */
.card {
  color: var(--color-text-primary);
}

Next Steps

You now know how to use and create components. For more details:

Summary

Remember:

  1. Use semantic CSS - .card-base, .btn-base, .badge-base
  2. Combine with modifiers - .card-intent-primary, .btn-size-lg
  3. Create macros for logic - When components need parameters or conditions
  4. Use CSS variables - Always use var(--token) not hardcoded values
  5. Prefix mixin components - .blog-card, not .card

With these patterns, you can build consistent, maintainable components across your site.