hugo

How to use page width in Hugo template?


I am using Hugo Universal theme, but saw that code in many other themes, too. To lay out a "table" of features, they use

[params.features]
    cols = 2 # Default: 3, Available values 2,3,4,6

That kinda works when resizing the page, but I would prefer to change number of columns based on the page width.

I was thinking about creating a CSS variable and set it from multiple

@media (max-width: 991px) {
    --col: 3
}
@media (max-width: 600px) {
    --col: 2
}

But I can't find a way to use that var in a template. Is it possible? Maybe with some JS code?

Or another way to use 2 columns on small, 3 on medium and 4 on large?

Update 8/13/2025 With the help of @SirPeople I almost got it. "Last" issue - what is this mysterious ::before empty grid cell in the image below? There is ::after after the last element, but that doesn't bother me too much. The real question is - how to get rid of it?

Update 2:

Solved with the help from before:: pseudo-element span all columns in grid

enter image description here


Solution

  • This does not seem feasible by default
    So if we inspect the code from Hugo Universal Theme. We can see how the parameters get injected into the theme itself:
    {{ $elements := default 3 .Site.Params.features.cols }}

    So the problem here is that Hugo being a static site builder, it is difficult to reverse it to inject the width of the screen as a parameter to modify $element variable.

    Do you still want to work around it?
    You could work around it modifying Hugo template, but this may have some risks. But basically, wrapping the rows with a grid, and then using media queries to set the col value in there. Like:

    <section class="bar background-white">
      <div class="container">
        <div class="row features-grid">
          {{ $features := sort .Site.Data.features "weight" }}
          {{ range $i, $element := $features }}
            <div class="col feature-item">
              <div class="box-simple">
                {{ with $element.url }}<a href="{{ $element.url }}">{{ end }}
                  <div class="icon">
                    <i class="{{ $element.icon }}"></i>
                  </div>
                {{ with $element.url }}</a>{{ end }}
                <h3>{{ $element.name | markdownify }}</h3>
                <p>{{ $element.description | markdownify }}</p>
              </div>
            </div>
          {{ end }}
        </div>
      </div>
    </section>
    

    And then:
    CSS

    :root {
      --col: 3; /* Let's use Hugo default :D */
    }
    
    @media (max-width: 991px) {
      :root {
        --col: 3;
      }
    }
    
    @media (max-width: 600px) {
      :root {
        --col: 2;
      }
    }
    
    .features-grid {
      display: grid;
      grid-template-columns: repeat(var(--col), 1fr);
      gap: 2rem;
    }
    
    .feature-item {
      display: flex;
      flex-direction: column;
    }
    

    Again, this is possible, but it will require you some customization and testing of all the layouts implied here. Specially given that you are kinda ignoring bootstrap for this part