If you like my work and appreciate my commitment, you can buy me a coffee.
If you want to customize this template to your own needs, please contact me! ( mix@proask.pl ) I offer professional help in customizing solutions that perfectly match your requirements. Write what you need, and together we will create something exceptional! 🚀
A Captive Portal allows you to force authentication or redirect to a clickable page to access the network. This is commonly used in hotspot networks, but is also widely used in corporate networks or small local area networks (e.g. shopping malls, restaurants, hotels, airports, etc.) as an additional layer of security for wireless or internet access.
OPNsense’s unique template manager makes setting up your own login page an easy task. At the same time it offers additional functionalities, such as:
- URL redirection
- Option for your own Pop-up
- Custom Splash page
To read more about the captive portal, I suggest you have a look here: OPNsense Captive Portal
Starting from version 2.6.0, the Captive Portal Template is identified under the name GuardianSuit.
The name is used for project identification, documentation, and further development. Earlier versions remain fully compatible, and the change does not affect configuration or usage.
The repository itself will not be replaced or migrated — only its name will change at a later stage. All history, issues, releases, and existing links will remain preserved by GitHub.
- Globe animation
- Globe animation WCAG
- Birds animation
- Cells animation
- Fog animation
- Halo animation
- Net animation
- Rings animation
- Waves animation
login: demo
password: demo
- Current and future versions of the template (v2.2.0 and above) are free for non-commercial use only.
- A commercial version will be released soon, including additional features and available for use in commercial projects.
- Features available in the free version remain compliant with the non-commercial use policy, and any premium features present in the commercial version will not be included in the GPL/free version.
The captive portal templates that I have seen so far most often lack multilingual support. I've always wondered why it should only be in English or only in one language at all? Well, let's look below. This template supports multilingualism, checks your preferred browser language, saves a cookie with information about which language was read or which language you chose using the selector. Uses language translations saved in the xx.json file. So, according to the layout, you can prepare your own translation, which you later have to declare in the settings.json file in the config directory.
The first (and probably most important) "default_lang" key specifies what language will be loaded by default when the Captive Portal client's web browser's preferred language is different from the languages supported by the platform.
"default_lang": "en"
In the current release, the settings key defines the default language that will be loaded in case the client browser language is not available in our available languages configuration:
"langs": {
"en": "English",
"pl": "Polski",
"sk": "Slovenčina",
"fr": "Français",
"de": "Deutsch",
"nl": "Nederlands",
"no": "Norsk",
"sv": "Svenska",
"fi": "Suomi",
"es": "Español",
"ca": "Català",
"ja": "日本語",
"ko": "한국어",
"zh": "中文",
"pt": "Português",
"it": "Italiano",
"da": "Dansk",
"cs": "Čeština",
"lt": "Lietuvių",
"lv": "Latviešu",
"et": "Eesti",
"el": "Ελληνικά",
"bg": "Български",
"ro": "Română",
"hr": "Hrvatski",
"ga": "Gaeilge",
"mt": "Malti",
"sl": "Slovenščina",
"hu": "Magyar",
"is": "Íslenska",
"sr": "Srpski",
"bs": "Bosanski",
"me": "Crnogorski",
"mk": "Македонски",
"sq": "Shqip",
"ka": "ქართული",
"hy": "Հայերեն",
"tr": "Türkçe",
"uk": "Українська",
"af": "Afrikaans",
"am": "አማርኛ",
"ar": "العربية",
"az": "Azərbaycanca",
"bn": "বাংলা",
"cy": "Cymraeg",
"eu": "Euskara",
"fa": "فارسی",
"tl": "Filipino",
"gl": "Galego",
"gu": "ગુજરાતી",
"he": "עברית",
"hi": "हिन्दी",
"id": "Bahasa Indonesia",
"mn": "Монгол",
"ms": "Bahasa Melayu",
"nb": "Norsk bokmål",
"ne": "नेपाली",
"si": "සිංහල",
"dz": "རྫོང་ཁ",
"sw": "Kiswahili",
"th": "ไทย",
"uz": "Oʻzbek",
"ur": "اُردُو",
"vi": "Tiếng Việt",
"zu": "Zulu",
"ky": "Кыргызча",
"dv": "ދިވެހި",
"ha": "Hausa",
"ku": "کوردی (سۆرانی)",
"kmr": "Kurdî (Kurmancî)",
"ps": "پښتو",
"sy": "ܣܘܪܝܝܐ",
"yi": "ייִדיש",
"rw": "Kinyarwanda",
"so": "Soomaali",
"tg": "Тоҷикӣ",
"mg": "Malagasy",
"my": "မြန်မာဘာသာ",
"km": "ភាសាខ្មែរ",
"lo": "ພາສາລາວ",
"ti": "ትግርኛ",
"rn": "Ikirundi",
"xh": "IsiXhosa",
"st": "Sesotho",
"tn": "Setswana",
"ss": "siSwati",
"nd": "isiNdebele",
"aa": "Qafaraf Af"
};
The langs_iso parameter, which was previously included in the configuration, has been removed from the manual settings. In the new template version, ISO values for languages are automatically generated based on the keys defined in the "langs" section.
Based on the keys of the langs variable object, the template will automatically determine whether it should display the LTR or RTL content.
If only one translation language is defined in the "langs" group, then the language switcher will not be included in the layout. The language translation will be based on the language defined in the "default_lang" key.
The base64 logo is set in the settings.json file. Due to the universal application, the frame of the image of the logo must keep the proportions of a square.
A mechanism that allows a specified number of failed login attempts. After exceeding the allowed number of attempts, the ability to log in will be blocked for a specified period of time.
Of course, this is not a perfect protection against an attempt to force credentials, but the Captive Portal in OPNsense does not yet have a similar protection.
"login": {
"control": false,
"attempts": 3,
"delay": 10
},
control- false: disabled, true: enabledattempts- Allowed number of login attemptsdelay- Time in minutes that must elapse before the next login
Version 2.6.0 introduces a fully configurable Offcanvas Menu, allowing administrators to extend the Captive Portal with additional navigation elements such as documentation, offers, privacy information, or custom links.
The menu can be enabled and positioned directly from the layout configuration group in settings.json:
"layout": {
"enable_rules": true|false,
"enable_menu": true|false,
"menu_position": "left|right",
"redirect_url": ""
},
enable_rules– Enables or disables the required consent to the provisions contained in the ISP provider's Regulations.enable_menu– Enables or disables the Offcanvas Menumenu_position– Defines the menu position (left or right)redirect_url– Redirection url address. If the value is not set or the set value is not a valid url address, the redirection functionality to the specified address will not be implemented.
Menu position automatically adapts to LTR / RTL layouts
On small screens, the menu width adjusts automatically for optimal usability
All menu elements are defined inside language JSON files (e.g. en.json, pl.json). This allows full multilingual support and easy customization without touching JavaScript code.
"menu": {
"title": "GuardianSuit Menu",
...
}
Each menu item is defined as an object with the following properties:
"item1": {
"title": "Internet",
"icon": "I",
"href": "https://yourdomain.com/#",
"target": "_blank"
}
title– Text displayed in the menuicon–- single character → rendered as a colored icon badge
- HTML entity → rendered as-is (e.g. §)
href–string→ standard linkobject→ modal definition
target–_blankor_self(in the modal definition the target parameter is not taken into account)
Menu items can open modal windows instead of links:
"item6": {
"title": "Privacy Policy",
"icon": "§",
"href": {
"type": "modal",
"title": "privacy_title",
"subtitle": "privacy_subtitle",
"content": "privacy_content",
"iconText": "§"
},
"target": "_self"
}
Modal content keys (title, subtitle, content) are resolved from the same language file, ensuring full translation support.
Menu items are organized using groups, which control visual grouping and order:
"group1": {
"title": "Offer",
"items": ["item1", "item2", "item3"]
}
Groups define logical sections inside the menu The items array references previously defined menu items Order in the array determines display order
Single-character icons automatically receive:
- unique background colors (non-repeating per render)
- automatic text color contrast (dark/light)
HTML entity icons are rendered without background styling
This ensures visual consistency and accessibility without manual styling
"layout": {
"lang_layout": "select",
"lang_flags_dir": "4x3",
},
flags-select– a drop-down selector with language names and flagsflags-only-select– a drop-down selector with flags only (no text)flags-list– a list of flags displayed side by side (no text)select– a classic drop-down selector with language names (no flags)
4x3– a 4:3 flag aspect ratio (standard rectangular)1x1– a square flag aspect ratio
"layout": {
"force_locales_data": true|false,
}
- When
true, updateslangsFlagsandlangISOMapfrom the browser'snavigator.language. - Only modifies entries if a region is present (e.g.,
en-CA,pt-BR). - Leaves two-letter languages (e.g.,
en,fr,es,pt) unchanged.
"layout": {
"a11y": true|false,
"a11y_contrast": true|false,
"a11y_keyboard": true|false,
"a11y_highlight": true|false,
"a11y_mono": true|false,
"a11y_helper": true|false,
"a11y_helper_breakpoint": 1200,
"a11y_factor": 0.5,
"a11y_threshold": 0.5,
},
-
a11y– global accessibility toggle - when false, other options are ignored -
a11y_contrast– automatically adjusts the color contrast of modals and UI elements to make them easier to read, this is based on thea11y_factoranda11y_treshholdparameters -
a11y_keyboard– enables keyboard shortcut support:- ⇧ Shift (left) + ⌥ Alt (left) + U - focus on the Username field
- ⇧ Shift (left) + ⌥ Alt (left) + P - focus on the Password field
- ⇧ Shift (left) + ⌥ Alt (left) + A - check/uncheck the "I accept the terms and conditions" checkbox
- ⇧ Shift (left) + ⌥ Alt (left) + R - opens a modal window with the terms and conditions
- ⇧ Shift (left) + ⌥ Alt (left) + I - click the active login button
- ⇧ Shift (left) + ⌥ Alt (left) + O - log out
- ⇧ Shift (left) + ⌥ Alt (left) + L - click the language switcher trigger
- ⇧ Shift (left) + ⌥ Alt (left) + M - click the Offcanvas menu trigger
Language shortcuts (⇧ Shift + two letters)
Hold ⇧ Shift and press two letters of the language code (ISO 639-1) at once or one after the otherExamples:
- ⇧ Shift + P + L → switches to Polish (pl)
- ⇧ Shift + E + N → switches to English (en)
- ⇧ Shift + D + E → switches to Deutsch (de)
- ⇧ Shift + F + R → switches to Français (fr)
- ⇧ Shift + E + S → switches to Español (es)
- ⇧ Shift + I + T → switches to Italiano (it)
-
a11y_highlight– highlights the currently focused element (e.g., input, button) to facilitate keyboard navigation -
a11y_mono– monochrome mode reduces the colors in the interface to visually simplify the UI (optional) -
a11y_helper– interactive Accessibility Tour Guide:- Provides a step-by-step, WCAG-compliant tour of the portal
- Highlights and explains all key elements, including username/password fields, login/logout buttons, the "accept rules" checkbox, and language selector
- Fully keyboard-navigable: use arrow keys to move between steps, Enter/Space to confirm, Esc to exit
- Dynamically adapts to the selected portal language
- Adds visual focus indicators to help users see which element is currently highlighted
-
a11y_helper_breakpoint- specifies the minimum viewport width (in pixels) at which the Accessibility Tour Guide trigger is displayed. On smaller screens (e.g., smartphones), the trigger is automatically hidden to avoid UI clutter -
a11y_factor– contrast adjustment factor fora11y_contrast- higher values = greater contrast increase -
a11y_threshold– contrast threshold – specifies the minimum contrast required between the background and text colors. If the current contrast is below this value, it is automatically adjusted.
"css_params": {
"bg_section": "#252828" ← 1 → Global background color of the entire login section "bg_image": "" ← 2 → Optional background image displayed over bg_section "bg_repeat": "no-repeat" ← 3 → Defines background image repeat behavior "bg_position": "center center" ← 4 → Position of the global background image "bg_size": "cover" ← 5 → Scaling method of the global background image "bg_attachment": "" ← 6 → Controls background image scroll behavior (fixed / scroll) "bg_color_left_side": "" ← 7 → Background color of the left panel "bg_img_left_side": "url('/images/bg_left_side.png')" ← 8 → Background image of the left panel "bg_img_left_side_repeat": "no-repeat" ← 9 → Repeat behavior of the left panel background image "bg_img_left_side_position": "top left" ← 10 → Position of the left panel background image "bg_img_left_side_size": "cover" ← 11 → Scaling method of the left panel background image "bg_img_left_side_attachment": "" ← 12 → Scroll behavior of the left panel background image "bg_left_side_blend": "linear-gradient(0deg, #005f6b4d 0%, #005f6bbf 83.85%)" ← 13 → Gradient overlay blended with the left panel background image "logo_bg_color": "rgba(249, 253, 255, 1)" ← 14 → Background color of the logo container "logo_bg_border_radius": "6px 6px 6px 6px" ← 15 → Border radius of the logo container "bg_color_right_side": "rgba(249, 253, 255, 1)" ← 16 → Background color of the right panel "bg_img_right_side": "" ← 17 → Background image of the right panel "bg_img_right_side_repeat": "no-repeat" ← 18 → Repeat behavior of the right panel background image "bg_img_right_side_position": "top left" ← 19 → Position of the right panel background image "bg_img_right_side_size": "cover" ← 20 → Scaling method of the right panel background image "bg_img_right_side_attachment": "" ← 21 → Scroll behavior of the right panel background image "bg_right_side_blend": "" ← 22 → Optional gradient overlay for the right panel background "left_side_shadow": "0 0 40px 0 rgba(0, 0, 0, .35)" ← 23 → Shadow cast by the left panel "right_side_shadow": "0 0 40px 0 rgba(0, 0, 0, .35)" ← 24 → Shadow cast by the right panel "bg_alternate": "#818a91" ← 25 → Alternate background color used for UI accents and borders "color_primary": "#7a7a7a" ← 26 → Main text color "color_secondary": "#ffffff" ← 27 → Secondary text color "color_alternate": "#373a3c" ← 28 → Alternate text color (labels, placeholders) "link_color": "#348893" ← 29 → Default link color "link_hover_color": "#f12184" ← 30 → Link hover color "input_field_color": "#e8e8e8" ← 31 → Text color inside input fields "input_field_label_color": "#373a3c" ← 32 → Label color for input fields "input_field_bg_color": "#ffffff" ← 33 → Background color of input fields "input_field_border_color": "rgba(145, 156, 167, .27)" ← 34 → Border color of input fields "input_field_border_radius": "5px 0px 0px 5px" ← 35 → Border radius of input fields "input_field_before_bg_color": "#00b5cb" ← 36 → Background color of input prefix element "input_field_after_bg_color": "#00b5cb" ← 37 → Background color of input suffix element "input_field_placeholder_color": "#373a3c" ← 38 → Placeholder text color "button_bg_color": "#00b5cb" ← 39 → Primary button background color "button_hover_bg_color": "#f12184" ← 40 → Primary button hover background color "button_color": "#ffffff" ← 41 → Primary button text color "button_hover_color": "#ffffff" ← 42 → Primary button hover text color "lang_switcher": "#00b5cb" ← 43 → Language switcher main background "lang_switcher_trigger": "#009db1" ← 44 → Language switcher trigger background "lang_switcher_link": "#5aecff" ← 45 → Language option link color "lang_switcher_hover": "#f12184" ← 46 → Language option hover background "lang_switcher_dropdown": "#216f7a" ← 47 → Language dropdown background "lang_switcher_dropdown_hover": "#f12184" ← 48 → Language dropdown hover background "fadein": "0.5s" ← 49 → Fade-in animation duration after page load "block_padding": "50px" ← 50 → Inner padding of left and right panels "left_side_block_radius": "15px 15px 15px 15px" ← 51 → Border radius of left panel content wrapper "right_side_block_radius": "15px 15px 15px 15px" ← 52 → Border radius of right panel content wrapper "helper_bg_color": "#00b5cb" ← 53 → Background color of the TourGuide helper button "helper_color": "#ffffff" ← 54 → Text/icon color of the TourGuide helper button "helper_size": "78px" ← 55 → Width and height of the TourGuide helper button "offcanvas_margin": "15px" ← 56 → Margin around the offcanvas menu "offcanvas_trigger_border_radius": "8px 8px 8px 8px" ← 57 → Border radius of the offcanvas trigger button "offcanvas_trigger_bg_color": "#00b5cb" ← 58 → Background color of the offcanvas trigger button "offcanvas_trigger_bg_hover_color": "#f12184" ← 59 → Background color of the offcanvas trigger button on hover "offcanvas_trigger_color": "#ffffff" ← 60 → Text/icon color of the offcanvas trigger button "offcanvas_trigger_hover_color": "#ffffff" ← 61 → Text/icon color of the offcanvas trigger button on hover "offcanvas_bg_color": "#ffffff" ← 62 → Background color of the offcanvas menu panel "offcanvas_border_radius": "8px 8px 8px 8px" ← 63 → Border radius of the offcanvas menu panel "offcanvas_text_color": "#373a3c" ← 64 → Main text color inside the offcanvas menu "offcanvas_link_bg_color": "transparent" ← 65 → Background color of offcanvas menu links "offcanvas_link_bg_hover_color": "#00b5cb1a" ← 66 → Background color of offcanvas menu links on hover "offcanvas_link_color": "#348893" ← 67 → Text color of offcanvas menu links "offcanvas_link_hover_color": "#f12184" ← 68 → Text color of offcanvas menu links on hover };
"modal": {
"auth_failed_header_color": "#f12184" Color of the header for the authentication failed modal "conn_failed_header_color": "#f12184" Color of the header for the connection failed modal "show_rules_header_color": "#4ca1af" Color of the header when showing ISP rules or regulations modal "bg_color": "#ffffff" Background color of the modal content area "icon_color": "rgba(255, 255, 255, .5)" Default color of icons inside the modal "overlay_color": "rgba(0,0,0,.3)" Background overlay color covering the page when modal is visible "timeout": 5000 Time in milliseconds after which the modal automatically closes (0 = no auto-close) "zindex": 1050 Z-index used for the modal to ensure it appears above other content };
"animate": {
"effect": "globe", Selected animation (available: birds, cells, fog, globe, halo, net, rings, waves) "params": { Common parameters for all effects "el": "#animate-js", CSS id where the animation will be embedded "bg_position": "center center", Set the position of the background "mouseControls": true, Controlling animation by mouse movement "touchControls": true, Controlling animation by swiping on the touch screen "gyroControls": false, Controlling animations with the gyrocompass of mobile devices "minHeight": 200.00, "minWidth": 200.00 }, "preset": { Animation presets ... Other configuration keys - here I refer to the Vanta.js online configurator https://www.vantajs.com/ } };
- Further work on the development of the template is planned, hence the bootstrap 5.3.3 and jquery 3.7.1 libraries have been included, at the same time libraries provided natively by OPNsense will not be used
- Some functions have been separated from the API, their notation has been changed
- A method for dynamically loading scripts into the template has been added - in the current version it is used by vanta.js dependencies, eventually it will be used more widely
- The layout has been changed, which was modeled on the Login Screen Design prepared by Ankur Tripathi
- CSS declarations have been improved, rtl support has been improved
- Particles.js has been abandoned, Vanta.js has been implemented in its place - thanks and respect to @tengbao - great job! The following effects are available: birds, cells, fog, globe, halo, net, rings and waves, which can be configured in a simplified way in settings.json in the animate key as the preferred effect, its params and the preset of the declared effect.
- Slovak translation included - thanks to @Gouster4.
- Optimizing the code of javascript functions.
- Splitting CSS into smaller portions, nesting CSS selectors.
- Blocking the ability to log into the system for a specified period of time, after a specified number of possible attempts.
- Logo update for OPNsense v25 - many thanks for the update and vigilance to @OctoCharm.
- Language selector layout modified.
- New translations generated using AI for multiple languages.
- Expanded digital accessibility support (WCAG).
- Introduced a new interactive Accessibility Tour Guide (TourGuide) compliant with WCAG 2.1 AA.
- Automatic locale and flag mapping has been added, updating flags and locales based on the browser's language while keeping two-letter language codes unchanged.
- Added a new Offcanvas Menu with configurable position and items.


