Using another .css file to modify React Components

Hello, I’m working on the reation of a static site for a professionnel work.

I’m building a Hugo site using the Blowfish theme and integrating some React components. Furthermore, I’m using a single custom.css file to update the style of my pages and some specific react components depending on their classes. Now, I’m trying to load an additional custom CSS file from assets/css/.

However, simply importing the file or adding it to customCSS in params.toml does not work, and I don’t know why.

Originally, my goal was to reuse a Modular Forms component (a Preact-based library) to build a contact form inside Hugo similar to this one: Playground: Login form (Preact) | Modular Forms

Since Hugo doesn’t support React natively, Preact is the recommended alternative (I call Preact with CDNs).

The problem is that the Modular Forms components rely on Tailwind CSS classes, but my Hugo site does not have Tailwind enabled at runtime. As a result, classes like bg-blue-500 or rounded-lg are not recognized, and the components render without styling.

Because Tailwind is not active in the final Hugo build, I had to manually recreate the required styles inside custom.css using plain CSS, refreshing it each time to update the site, and making the file even bigger.

This is not ideal, and it’s ind of annoying since plan to add more react components in the future. I would prefer to either use Tailwind directly or load additional CSS files properly from assets/css/.

Project Architecture:

inovStatique/
├── assets/
│   └── css/
│       ├── custom.css
│       └── contact.css   (not loaded by Hugo)
│
├── static/
│
├── content/
│   ├── _index.md/
│   ├── Welcome/
│   ├── Team/
│   ├── Projects/
│   ├── Services/
│   └── Contact/
│       ├── _index.md
│       └── contact.md
│
├── layouts/
│   ├── partials/
│   │   └── preact-contact-script.html 
│   └── shortcodes/
│       └── contact-modular-forms.html     
│
├── themes/
│   └── blowfish/
│
├── config/_default/
│   └── params.toml

Params.toml:

# -- Theme Options --
# These options control how the theme functions and allow you to
# customise the display of your website.
#
# Refer to the theme docs for more details about each of these parameters.
# https://blowfish.page/docs/configuration/#theme-parameters

colorScheme = "blowfish"
defaultAppearance = "light"
autoSwitchAppearance = false
disableThemeToggle = true

[params]
  customCSS = ["css/custom.css"]

enableA11y = false
enableSearch = true
enableCodeCopy = false
enableStructuredBreadcrumbs = false

replyByEmail = false

# mainSections = ["section1", "section2"]
# robots = ""

disableImageOptimization = false
disableImageOptimizationMD = false
disableTextInHeader = false
# backgroundImageWidth = 1200

# defaultBackgroundImage = "IMAGE.jpg" # used as default for background images
# defaultFeaturedImage = "IMAGE.jpg" # used as default for featured images in all articles
# defaultSocialImage = "/android-chrome-512x512.png" # used as default for social media sharing (Open Graph and Twitter)
hotlinkFeatureImage = false
# imagePosition = "50% 50%"

# highlightCurrentMenuArea = true
# smartTOC = true
# smartTOCHideUnfocusedChildren = true

fingerprintAlgorithm = "sha512" # Valid values are "sha512" (default), "sha384", "sha256"

giteaDefaultServer = "https://git.fsfe.org"
forgejoDefaultServer = "https://v11.next.forgejo.org"

[header]
  layout = "basic" # valid options: basic, fixed, fixed-fill, fixed-gradient, fixed-fill-blur
  showTitle = false

[footer]
  showMenu = true
  showCopyright = true
  showThemeAttribution = true
  showAppearanceSwitcher = false
  showScrollToTop = true

[homepage]
  layout = "page"
  homepageImage = ""   # image de fond
  logo = "inov_logo-bg-free.png"                 # logo centré
  showRecent = false
  layoutBackgroundBlur = false
  disableHeroImageFilter = true

[page]
  showHero = false
  heroStyle = "background"
  

[article]
  showDate = false
  showViews = false
  showLikes = false
  showDateOnlyInArticle = false
  showDateUpdated = false
  showAuthor = false
  # showAuthorBottom = false
  showHero = false
  heroStyle = "background"
  layoutBackgroundBlur = true # only used when heroStyle equals background or thumbAndBackground
  layoutBackgroundHeaderSpace = true # only used when heroStyle equals background
  showBreadcrumbs = false
  showDraftLabel = true
  showEdit = false
  # editURL = "https://github.com/username/repo/"
  editAppendPath = true
  seriesOpened = false
  showHeadingAnchors = false
  showPagination = false
  invertPagination = false
  showReadingTime = false
  showTableOfContents = false
  # showRelatedContent = false
  # relatedContentLimit = 3
  showTaxonomies = false # use showTaxonomies OR showCategoryOnly, not both
  showCategoryOnly = false # use showTaxonomies OR showCategoryOnly, not both
  showAuthorsBadges = false
  showWordCount = false
  # sharingLinks = [ "linkedin", "twitter", "bluesky", "mastodon", "reddit", "pinterest", "facebook", "email", "whatsapp", "telegram", "line"]
  showZenMode = false

[list]
  showHero = false
  # heroStyle = "background" # valid options: basic, big, background, thumbAndBackground
  layoutBackgroundBlur = true # only used when heroStyle equals background or thumbAndBackground
  layoutBackgroundHeaderSpace = true # only used when heroStyle equals background
  showBreadcrumbs = false
  showSummary = false
  showViews = false
  showLikes = false
  showTableOfContents = false
  showCards = false
  orderByWeight = false
  groupByYear = false
  cardView = false
  cardViewScreenWidth = false
  constrainItemsWidth = false

[sitemap]
  excludedKinds = ["taxonomy", "term"]

[taxonomy]
  showTermCount = true
  showHero = false
  # heroStyle = "background" # valid options: basic, big, background, thumbAndBackground
  showBreadcrumbs = false
  showViews = false
  showLikes = false
  showTableOfContents = false
  cardView = false

[term]
  showHero = false
  # heroStyle = "background" # valid options: basic, big, background, thumbAndBackground
  showBreadcrumbs = false
  showViews = false
  showLikes = false
  showTableOfContents = true
  groupByYear = false
  cardView = false
  cardViewScreenWidth = false

[firebase]
  # apiKey = "XXXXXX"
  # authDomain = "XXXXXX"
  # projectId = "XXXXXX"
  # storageBucket = "XXXXXX"
  # messagingSenderId = "XXXXXX"
  # appId = "XXXXXX"
  # measurementId = "XXXXXX"

[fathomAnalytics]
  # site = "ABC12345"
  # domain = "llama.yoursite.com"

[umamiAnalytics]
  # websiteid = "ABC12345"
  # domain = "llama.yoursite.com"
  # dataDomains = "yoursite.com,yoursite2.com"
  # scriptName = ""
  # enableTrackEvent = true

[selineAnalytics]
  # token = "XXXXXX"
  # enableTrackEvent = true

[buymeacoffee]
  # identifier = ""
  # globalWidget = true
  # globalWidgetMessage = "Hello"
  # globalWidgetColor = "#FFDD00"
  # globalWidgetPosition = "right"

[verification]
  # google = ""
  # bing = ""
  # pinterest = ""
  # yandex = ""
  # fediverse = ""

[rssnext]
  # feedId = ""
  # userId = ""

[advertisement]
  # adsense = ""

preact-contact-script.html

contact-modular-forms.html

{{ partial “preact-contact-script.html” . }}

custom.css

@import “./contact.css”;

/* Élargir les zones de contenu Blowfish */
.prose,
.container,
.max-w-prose,
.max-w-3xl {
max-width: 1200px !important;
}

/* Centrer proprement */
.prose,
.container {
margin-left: auto !important;
margin-right: auto !important;
}

/* Ajouter un padding horizontal pour éviter que le texte touche les bords */
.prose,
.container {
padding-left: 1.5rem;
padding-right: 1.5rem;
}

/* Corriger la hiérarchie des titres dans Blowfish /
.prose h2 {
font-size: 2rem !important; /
32px */
font-weight: 700 !important;
}

.prose h3 {
font-size: 1.5rem !important; /* 24px */
font-weight: 600 !important;
}

/* Agrandir les paragraphes /
.prose p, li {
font-size: 1.25rem !important; /
19.2px */
line-height: 1.75 !important;
color: rgba(var(–color-neutral-500), 1);
}

/* Agrandir les légendes des images /
.prose figcaption {
font-size: 1.05rem !important; /
16.8px */
line-height: 1.5 !important;
color: #444 !important;
text-align: center !important;
margin-top: 0.5rem !important;
}

.main-menu {
position: sticky;
top: 0;
z-index: 1000;
background-color: white;
}

.contact-form {
border: none !important;
box-shadow: none !important;
}

/* .contact-button {
width: 100%;
background-color: #2563eb;
color: #ffffff;
padding: 0.75rem 1rem;
border-radius: 0.5rem;
font-weight: 600;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
border: none;
cursor: pointer;
transition: background-color 150ms ease, transform 150ms ease;
}

.contact-button:hover {
background-color: #1d4ed8;
}

.contact-button:focus {
outline: 2px solid #2563eb;
outline-offset: 2px;
} */

/* Taille du label /
.contact-label {
display: inline-block;
font-weight: 500;
font-size: 1.125rem; /
text-lg */
margin-bottom: 0.5rem;
}

@media (min-width: 1024px) {
.contact-label {
font-size: 1.25rem; /* text-xl */
margin-bottom: 1.25rem;
}
}

/* Astérisque rouge /
.contact-label-required {
margin-left: 0.25rem;
color: #dc2626; /
red-600 */
}

@media (prefers-color-scheme: dark) {
.contact-label-required {
color: #f87171; /* red-400 */
}
}

/* Couleur inversée /
.contact-title {
font-size: 1.5rem; /
text-2xl /
color: #e2e8f0; /
slate-200 */
margin-bottom: 1rem;
font-weight: 600;
}

@media (prefers-color-scheme: dark) {
.contact-title {
color: #0f172a; /* slate-900 */
}
}

/* Responsiveness identique à ton exemple /
@media (min-width: 768px) {
.contact-title {
font-size: 1.875rem; /
md:text-3xl */
}
}

@media (min-width: 1024px) {
.contact-title {
font-size: 2.25rem; /* lg:text-4xl */
}
}

/* Style de base des inputs /
.contact-input {
width: 100%;
padding: 0.75rem 1rem;
border: 2px solid #475569; /
slate-600 : gris sombre */
border-radius: 0.75rem;
background: white;
transition: border-color 150ms ease;
}

/* Effet hover sombre (comme ModularForms mais inversé) /
.contact-input:hover {
border-color: #334155; /
slate-700 : encore plus sombre */
}

contact.css

.contact-button {
width: 100%;
background-color: #2563eb; /* bleu */
color: #ffffff;
padding: 0.75rem 1rem;
border-radius: 0.5rem;
font-weight: 600;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
border: none;
cursor: pointer;
transition: background-color 150ms ease, transform 150ms ease;
}

.contact-button:hover {
background-color: #1d4ed8; /* bleu plus foncé */
}

.contact-button:focus {
outline: 2px solid #2563eb;
outline-offset: 2px;
}

Versions used

  • Hugo: v0.152.2
  • Blowfish: v2.93.0
  • React:19.2.3
  • NPM: 11.6.2
  • Tailwind: 4.1.18

So, I need a clean way to:

  • Load additional CSS files from assets/css/
    OR
  • Enable Tailwind globally so Modular Forms components render correctly
    OR
  • Integrate Preact + Tailwind in a way that works with Hugo and Blowfish

Answer me as soon as possible please, it’s for an important work.

Hi, here are a few notes:

  1. Blowfish’s documentation does not mention a customCSS parameter, so it’s expected that adding it in params.toml has no effect.

  2. My approach is to build your own Tailwind CSS. See this comment

  3. Blowfish supports inserting custom CSS conditionally per page by extending the head. For example:

    <!-- layouts/partials/extend-head-uncached.html -->
    {{ if hasPrefix .Page.Path "/events" }}
      {{ with resources.Get "css/events.css" }}
        {{ $eventsCSS := . | resources.Minify | resources.Fingerprint (site.Params.fingerprintAlgorithm | default "sha512") }}
        <link rel="stylesheet" href="{{ $eventsCSS.RelPermalink }}" integrity="{{ $eventsCSS.Data.Integrity }}">
      {{ end }}
    {{ end }}
    

Hope this helps :+1: