Hugo Scroll theme has no gallery shortcode. Some alternatives are discussed in #70. We will pick the gallery-slider from gethugothemes/hugo-modules.
This post is quite short. Index:
Installation
Follow the steps of gethugothemes/hugo-modules.
Modules
$EDITOR hugo.toml. Add the gallery-slider module:
[[module.imports]]
path = "github.com/gethugothemes/hugo-modules/gallery-slider"
disable = false
For example, a Hugo Scroll theme user needs:
[module]
proxy = "direct"
[module.hugoVersion]
extended = true
min = "0.132.0"
[[module.imports]]
path = "github.com/zjedi/hugo-scroll"
disable = false
[[module.imports]]
path = "github.com/gethugothemes/hugo-modules/gallery-slider"
disable = false
Note. The Hugo Scroll theme installation via Hugo modules was explained here.
Then run:
hugo mod get github.com/gethugothemes/hugo-modules/gallery-slider@latest
Check it with hugo mod graph.
> hugo mod graph
project github.com/zjedi/hugo-scroll@v0.0.0-20250604223730-54f7b8543f18+vendor
project github.com/gethugothemes/hugo-modules/gallery-slider@v0.0.0-20250702070945-cd8319c6b26e+vendor
Optionally run hugo mod vendor to create a local copy:
> tree _vendor/github.com/gethugothemes/hugo-modules/gallery-slider/
_vendor/github.com/gethugothemes/hugo-modules/gallery-slider/
├── assets
│ ├── css
│ │ └── gallery-slider.css
│ ├── js
│ │ └── gallery-slider.js
│ ├── plugins
│ │ ├── glightbox
│ │ │ ├── glightbox.css
│ │ │ └── glightbox.js
│ │ └── swiper
│ │ ├── swiper-bundle.css
│ │ └── swiper-bundle.js
│ └── scss
│ └── gallery-slider.scss
└── layouts
├── partials
│ ├── gallery.html
│ ├── image-pipe.html
│ └── slider.html
└── shortcodes
├── gallery.html
└── slider.html
Plugins
$EDITOR hugo.toml and add
[[params.plugins.css]]
link = "plugins/swiper/swiper-bundle.min.css"
[[params.plugins.css]]
link = "plugins/glightbox/glightbox.min.css"
[[params.plugins.js]]
link = "plugins/swiper/swiper-bundle.min.js"
[[params.plugins.js]]
link = "plugins/glightbox/glightbox.min.js"
[[params.plugins.js]]
link = "js/gallery-slider.js"
As #16 points out, just previous steps are not enough:
I didn’t get
glightbox.jsorglightbox.cssfiles instaticfolder of my project
The guide misses next step:
I managed to figure out how to implement. All resources should be processed with a hugo pipeline.
Lets do it.
$EDITOR layouts/partials/custom_head.html to append:
{{ $swiperCSS := resources.Get "plugins/swiper/swiper-bundle.css" }}
{{ $glightboxCSS := resources.Get "plugins/glightbox/glightbox.css" }}
{{ $gallerySliderCSS := resources.Get "css/gallery-slider.css" }}
{{ $swiperJS := resources.Get "plugins/swiper/swiper-bundle.js" }}
{{ $glightboxJS := resources.Get "plugins/glightbox/glightbox.js" }}
{{ $gallerySliderJS := resources.Get "js/gallery-slider.js" }}
{{ if $swiperCSS }}<link rel="stylesheet" href="{{ $swiperCSS.RelPermalink }}">{{ end }}
{{ if $glightboxCSS }}<link rel="stylesheet" href="{{ $glightboxCSS.RelPermalink }}">{{ end }}
{{ if $gallerySliderCSS }}<link rel="stylesheet" href="{{ $gallerySliderCSS.RelPermalink }}">{{ end }}
{{ if $swiperJS }}<script src="{{ $swiperJS.RelPermalink }}"></script>{{ end }}
{{ if $glightboxJS }}<script src="{{ $glightboxJS.RelPermalink }}"></script>{{ end }}
{{ if $gallerySliderJS }}<script src="{{ $gallerySliderJS.RelPermalink }}"></script>{{ end }}
Actually also enable a initializer:
{{ $gallerySliderInitJS := resources.Get "js/gallery-slider-init.js" | minify | fingerprint }}
<script src="{{ $gallerySliderInitJS.RelPermalink }}" defer></script>
$EDITOR assets/js/gallery-slider-init.js:
// Wait for the page to fully load and content to be rendered
function initializeSliders() {
const sliders = document.querySelectorAll('.gallery-slider');
console.log('Found sliders to initialize:', sliders.length);
sliders.forEach(slider => {
// Skip if already initialized
if (slider.swiper) {
console.log('Slider already initialized');
return;
}
// Initialize Swiper
try {
const swiper = new Swiper(slider, {
slidesPerView: 1,
loop: true,
spaceBetween: 30,
navigation: {
nextEl: slider.querySelector('.swiper-button-next'),
prevEl: slider.querySelector('.swiper-button-prev'),
},
on: {
init: function() {
console.log('Swiper initialized successfully!');
}
}
});
} catch (error) {
console.error('Swiper initialization error:', error);
}
});
}
// Initialize after a short delay to ensure DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', function() {
// Small delay to ensure shortcodes are rendered
setTimeout(initializeSliders, 100);
});
} else {
// DOM already loaded, but wait for shortcodes
setTimeout(initializeSliders, 100);
}
// Also try after all resources are loaded
window.addEventListener('load', function() {
setTimeout(initializeSliders, 200);
});
// MutationObserver as fallback for dynamically added content
if (typeof MutationObserver !== 'undefined') {
const observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (mutation.addedNodes.length) {
// Check if any new nodes contain sliders
const hasNewSliders = Array.from(mutation.addedNodes).some(node => {
return node.nodeType === 1 && (
node.classList?.contains('gallery-slider') ||
node.querySelector?.('.gallery-slider')
);
});
if (hasNewSliders) {
setTimeout(initializeSliders, 50);
}
}
});
});
observer.observe(document.body, {
childList: true,
subtree: true
});
}
Usage
In a markdown file use the slider shortcode via {{< slider dir="images/...." >}}:
For example my Instagram mosaic generator post has next images. Move your cursor inside the picture to see the right arrow, then click it.
More parameters can be passed. Check:
- The slider shortcode parameters at gethugothemes/hugo-modules
- My post about Hugo shortcodes
The arrows to slide might need customization. The white color can be confused with the background of the picture.
In layouts/partials/custom_head.html we could edit the style, since the arrow color is defined via a SCSS variable:
:root {
[...]
--swiper-navigation-color: #246B9F;
}
Though this has no effect.
Inspecting in the browser we check that the
field-value of color: var(--swiper-navigation-color, var(--swiper-theme-color)).swiper-button-next is striked.
I.e. this SCSS is overridden by a more specific rule.
In that same HTML file add in <style>:
.gallery-slider .swiper-button-next::after,
.gallery-slider .swiper-button-prev::after {
color: #ffffff;
background-color: #246B9F;
border-radius: 100%;
}
Online images
To use non-local images create a new shortcode.
List of links to pictures
$EDITOR layouts/shortcodes/slider-external.html with code:
{{ $loading := .Get "loading" | default "lazy" }}
{{ $zoomable := .Get "zoomable" | default "true" }}
<div class="swiper gallery-slider">
<div class="swiper-wrapper">
{{ range $index := seq 1 20 }}
{{ $imageParam := printf "image%d" $index }}
{{ $imageUrl := $.Get $imageParam }}
{{ if $imageUrl }}
{{ $altParam := printf "alt%d" $index }}
{{ $imageAlt := $.Get $altParam | default (printf "Image %d" $index) }}
<div class="swiper-slide {{ if eq $zoomable `true` }}zoomable{{ end }}">
{{ if eq $zoomable `true` }}
<a href="{{ $imageUrl }}" class="glightbox" style="display: block;">
<img
loading="{{ $loading }}"
src="{{ $imageUrl }}"
class="img"
style="[...]"
alt="{{ $imageAlt }}" />
</a>
{{ else }}
<img
loading="{{ $loading }}"
src="{{ $imageUrl }}"
class="img"
style="[...]"
alt="{{ $imageAlt }}" />
{{ end }}
</div>
{{ end }}
{{ end }}
</div>
<span class="swiper-button-prev"></span>
<span class="swiper-button-next"></span>
</div
Example usage:
{{< slider-external
image1="https://hugocodex.org/uploads/slider/image1.jpg"
image2="https://hugocodex.org/uploads/slider/image2.jpg"
>}}
Which renders to:
Btw. This images are courtesy of https://hugocodex.org/add-ons/slider-carousel/, another alternative suggested in #70.
JSON format
Previous shortcode did not accept the link to redirect to, nor the alt-ernative text if the picture is not found.
With layouts/shortcodes/slider-external-json.html we add those features.
{{ $loading := .Get "loading" | default "lazy" }}
{{ $zoomable := .Get "zoomable" | default "true" }}
{{ $jsonData := .Inner | transform.Unmarshal }}
{{ if $jsonData }}
<div class="swiper gallery-slider">
<div class="swiper-wrapper">
{{ range $index, $item := $jsonData }}
{{ if $item.src }}
{{ $href := $item.href | default $item.src }}
{{ $alt := $item.alt | default $item.src }}
<div class="swiper-slide {{ if eq $zoomable `true` }}zoomable{{ end }}">
{{ if eq $zoomable `true` }}
<a href="{{ $href }}" class="glightbox" style="display: block;">
<img
loading="{{ $loading }}"
src="{{ $item.src }}"
class="img"
style="[...]"
alt="{{ $alt }}" />
</a>
{{ else }}
<img
loading="{{ $loading }}"
src="{{ $item.src }}"
class="img"
style="[...]"
alt="{{ $alt }}" />
{{ end }}
</div>
{{ else }}
{{ errorf "Swiper gallery item %d is missing 'src' field" $index }}
{{ end }}
{{ end }}
</div>
<span class="swiper-button-prev"></span>
<span class="swiper-button-next"></span>
</div>
{{ else }}
{{ errorf "Invalid JSON in swiper_gallery shortcode: %s" .Inner }}
{{ end }}
Example usage:
- First and second image with all three fields explicitly set.
- Third image lacks
altfield. Thus, it defaults tosrcfield-value. - Forth JSON object is missing
hreffield. Therefore, it falls tosrc. - Fifth picture contains only
src. So rest of fields are default to thesrcvalue. - The last image is called still with a valid JSON, it’s like the previous picture but with a
srcnot to be found (invalid URL) to test thealtdisplay render directly (no need to inspect the HTML code rendered).
{{< slider-external-json loading="lazy" >}}
[
{
"src": "https://hugocodex.org/uploads/slider/image1.jpg",
"href": "https://hugocodex.org/add-ons/slider-carousel/",
"alt": "Hugo Codex Slider/Carousel image 1"
},
{
"src": "https://hugocodex.org/uploads/slider/image2.jpg",
"href": "https://hugocodex.org/add-ons/slider-carousel/",
"alt": "Hugo Codex Slider/Carousel image 2"
},
{
"src": "https://images.pexels.com/photos/1108099/pexels-photo-1108099.jpeg",
"href": "https://www.pexels.com/photo/two-yellow-labrador-retriever-puppies-1108099/"
},
{
"src": "https://images.pexels.com/photos/406014/pexels-photo-406014.jpeg",
"alt": "Closeup Photo of Brown and Black Dog Face"
},
{
"src": "https://images.pexels.com/photos/7210754/pexels-photo-7210754.jpeg"
},
{
"src": "URL_no_valid_for_testing"
}
]
{{< /slider-external-json >}}
Which renders to next HTML.
Note the data-swiper-slide-index starts in 5, then 0 to 5, and ends in 0; do not overthink it, somehow this is how the slider JavaScript works.
1<div class="swiper gallery-slider swiper-initialized swiper-horizontal swiper-pointer-events swiper-backface-hidden">
2 <div class="swiper-wrapper" [...]>
3 <div class="swiper-slide zoomable swiper-slide-duplicate swiper-slide-prev"
4 data-swiper-slide-index="5" [...]>
5 <a href="URL_no_valid_for_testing" [...]>
6 <img loading="lazy"
7 class="img" style="[...]"
8 src="URL_no_valid_for_testing"
9 alt="URL_no_valid_for_testing">
10 </a>
11 </div>
12 <div class="swiper-slide zoomable swiper-slide-active"
13 data-swiper-slide-index="0" [...]>
14 <a href="https://hugocodex.org/add-ons/slider-carousel/" [...]>
15 <img loading="lazy"
16 class="img" style="[...]"
17 src="https://hugocodex.org/uploads/slider/image1.jpg"
18 alt="Hugo Codex Slider/Carousel image 1">
19 </a>
20 </div>
21 <div class="swiper-slide zoomable swiper-slide-next"
22 data-swiper-slide-index="1" [...]>
23 <a href="https://hugocodex.org/add-ons/slider-carousel/" [...]>
24 <img loading="lazy"
25 class="img" style="[...]"
26 src="https://hugocodex.org/uploads/slider/image2.jpg"
27 alt="Hugo Codex Slider/Carousel image 2">
28 </a>
29 </div>
30 <div class="swiper-slide zoomable"
31 data-swiper-slide-index="2" [...]>
32 <a href="https://www.pexels.com/photo/two-yellow-labrador-retriever-puppies-1108099/" [...]>
33 <img loading="lazy"
34 class="img" style="[...]"
35 src="https://images.pexels.com/photos/1108099/pexels-photo-1108099.jpeg"
36 alt="https://images.pexels.com/photos/1108099/pexels-photo-1108099.jpeg">
37 </a>
38 </div>
39 <div class="swiper-slide zoomable"
40 data-swiper-slide-index="3" [...]>
41 <a href="https://images.pexels.com/photos/406014/pexels-photo-406014.jpeg" [...]>
42 <img loading="lazy"
43 class="img" style="[...]"
44 src="https://images.pexels.com/photos/406014/pexels-photo-406014.jpeg"
45 alt="Closeup Photo of Brown and Black Dog Face">
46 </a>
47 </div>
48 <div class="swiper-slide zoomable"
49 data-swiper-slide-index="4" [...]>
50 <a href="https://images.pexels.com/photos/7210754/pexels-photo-7210754.jpeg" [...]>
51 <img loading="lazy"
52 class="img" style="[...]"
53 src="https://images.pexels.com/photos/7210754/pexels-photo-7210754.jpeg"
54 alt="https://images.pexels.com/photos/7210754/pexels-photo-7210754.jpeg">
55 </a>
56 </div>
57 <div class="swiper-slide zoomable swiper-slide-duplicate-prev"
58 data-swiper-slide-index="5" [...]>
59 <a href="URL_no_valid_for_testing" [...]>
60 <img loading="lazy"
61 class="img" style="[...]"
62 src="URL_no_valid_for_testing"
63 alt="URL_no_valid_for_testing">
64 </a>
65 </div>
66 <div class="swiper-slide zoomable swiper-slide-duplicate swiper-slide-duplicate-active"
67 data-swiper-slide-index="0"[...]>
68 <a href="https://hugocodex.org/add-ons/slider-carousel/" [...]>
69 <img loading="lazy"
70 class="img" style="[...]"
71 src="https://hugocodex.org/uploads/slider/image1.jpg"
72 alt="Hugo Codex Slider/Carousel image 1">
73 </a>
74 </div>
75 </div>
76 <span class="swiper-button-prev" [...]></span>
77 <span class="swiper-button-next" [...]></span>
78 <span class="swiper-notification" [...]></span>
79</div>Test the hrefs and alts directly in:









