🆕 Bricks 2.4 已发布:查询循环性能提升 40%,迁移教程同步更新 →

首页 / 中文教程 / 教程

Bricks 内容切换器:纯 CSS 方案(radio + :has 实现标签切换)

实战技巧:Bricks 内容切换器:纯 CSS 方案(radio + :has 实现标签切换)——css 实操步骤+踩坑提醒,适合外贸独立站与 WordPress 开发者。

Ray ChanRay Chan·2026-08-12·约 4 分钟
目录
  1. 1.DOM 结构
  2. 2.纯 CSS 方法
  3. 3.Firefox 兼容
  4. 4.结论

本教程教你用 100% 纯 CSS 在 Bricks 里实现内容切换器(content switcher)——在多个内容区块间切换显示,典型场景如价格表(pricing tables)切换、功能特性切换,不需要任何 JS。

如果你想要 Bricks 内置的集成方案,可以直接购买 BricksExtras 插件;但如果你愿意动手写自定义 CSS,这篇教程正合适。

DOM 结构

先在 Bricks 里搭出下面的 DOM 结构,给每个元素按如下规则分配 class:

  • 第一个元素是代码块(放 HTML)

  • revealed-items 可以是任意元素(就是被切换显示的内容块)

纯 CSS 方法

HTML

把下面这段 HTML 粘贴到代码块里:

<fieldset class="radio-switch">
  <!-- First Item -->
  <input type="radio" name="switch" id="item1">
  <label for="item1">Label 1</label>
  <!-- Second Item -->
  <input type="radio" name="switch" id="item2" checked>
  <label for="item2">Label 2</label>
  <!-- Third Item -->
  <input type="radio" name="switch" id="item3">
  <label for="item3">Label 3</label>
  <div class="highlight"></div>
</fieldset>

标签文字(Label)可以按需修改,但不要改动属性,否则动画会失效。示例用了 3 个标签/输入项,CSS 原生支持到 4 个——想增减就按需增删条目。

第二个标签默认是选中态,因为它的 <input> 末尾带了 checked 属性;想默认选中哪个,就把 checked 移到哪个条目上。

CSS

把下面的 CSS 粘贴到页面:

/* CSS Variables */
fieldset.radio-switch {
   --switch-item-number: 3;
   --switch-padding: 5px;
   --switch-bg: #fff;
   --switch-border-width: 1px;
   --switch-border-color: #ccc;
   --switch-border-radius: 50px;
   --switch-label-padding: 1.5rem 0.5rem;
   --switch-inactive-text-color: #666;
   --switch-active-text-color: #fff;
   --switch-active-bg-color: #350792;
}
/* Fieldset*/
fieldset.radio-switch {
   display: flex;
   position: relative;
   padding: 0;
   outline: 0;
   border: var(--switch-border-width) solid var(--switch-border-color);
   background-color: var(--switch-bg);
   border-radius: var(--switch-border-radius);
   overflow: hidden;
   width: 100%;
}
/* Hide Imputs*/
fieldset.radio-switch input {
   visibility: hidden;
   opacity: 0;
   display: none !important;
}
/* Labels */
fieldset.radio-switch label {
   z-index: 1;
   margin: 0;
   padding: var(--switch-label-padding);
   border-radius: var(--switch-border-radius);
   cursor: pointer;
   color: var(--switch-inactive-text-color);
   font-size: 14px;
   line-height: 1.4em;
   font-weight: 600;
}
/* Active Labels*/
fieldset.radio-switch input:checked+label {
   color: var(--switch-active-text-color);
   cursor: default;
}
/* Highlight */
fieldset.radio-switch label,
fieldset.radio-switch .highlight {
   width: calc(100% / var(--switch-item-number));
   display: flex;
   align-items: center;
   justify-content: center;
   text-align: center;
   transition: 300ms transform ease-in-out,
      300ms color ease-in-out;
}
fieldset.radio-switch .highlight {
   position: absolute;
   background-color: var(--switch-active-bg-color);
   top: calc(var(--switch-padding) / 2);
   left: calc(var(--switch-padding) / 2);
   bottom: 0;
   right: 0;
   border-radius: var(--switch-border-radius);
   height: calc(100% - var(--switch-padding));
   width: calc((100% / var(--switch-item-number)) - calc(var(--switch-padding) / var(--switch-item-number)));
}
/* Moving Highliht*/
fieldset.radio-switch input:nth-of-type(2):checked~.highlight {
   transform: translateX(100%);
}
fieldset.radio-switch input:nth-of-type(3):checked~.highlight {
   transform: translateX(200%);
}
fieldset.radio-switch input:nth-of-type(4):checked~.highlight {
   transform: translateX(300%);
}
/* Revealed Items */
body.bricks-is-frontend .revealed-items {
   display: none !important;
}
/* Active Reveald Item */
body.bricks-is-frontend .switcher-wrapper:has(input:nth-of-type(1):checked)~.revealed-items.item1,
body.bricks-is-frontend .switcher-wrapper:has(input:nth-of-type(2):checked)~.revealed-items.item2,
body.bricks-is-frontend .switcher-wrapper:has(input:nth-of-type(3):checked)~.revealed-items.item3,
body.bricks-is-frontend .switcher-wrapper:has(input:nth-of-type(4):checked)~.revealed-items.item4 {
   display: flex !important;
   animation: 300ms activeItems ease-in-out;
}
/* Custom Entrance Animation */
@keyframes activeItems {
   0% {
      transform: translateY(40px);
      opacity: 0;
   }
   100% {
      transform: translateY(0);
      opacity: 1;
   }
}

注意:第一段是 CSS 变量,方便你按需调整样式。其中 --switch-item-number 是必填变量,代表切换器里的标签数量——示例用了 3 个标签所以填 3;增删 HTML 条目时记得同步修改这个数字。

移动端竖排(可选)

想在手机上把切换器竖着排列,追加这段 CSS:

@media screen and (max-width: 767px) {
   fieldset.radio-switch {
      --switch-border-radius: 15px;
      flex-direction: column;
   }
   /* Highlight */
   fieldset.radio-switch .highlight,
   fieldset.radio-switch label {
      width: calc(100% - var(--switch-padding));
      height: calc((100% / var(--switch-item-number)) - calc(var(--switch-padding) / var(--switch-item-number)));
   }
   /* Moving Highliht*/
   fieldset.radio-switch input:nth-of-type(2):checked~.highlight {
      transform: translateY(100%);
   }
   fieldset.radio-switch input:nth-of-type(3):checked~.highlight {
      transform: translateY(200%);
   }
   fieldset.radio-switch input:nth-of-type(4):checked~.highlight {
      transform: translateY(300%);
   }
}

Firefox 兼容

早期 Firefox 不支持 :has() 伪类,需要做降级处理:先把默认显示的 revealed item 加上 .active 类,然后修改 CSS(把 :has() 那几行注释掉,改用 .active 类触发显示),最后加一段 JS 在点击时切换 .active 类:

/* Active Reveald Item */
/*
body.bricks-is-frontend .switcher-wrapper:has(input:nth-of-type(1):checked)~.revealed-items.item1,
body.bricks-is-frontend .switcher-wrapper:has(input:nth-of-type(2):checked)~.revealed-items.item2,
body.bricks-is-frontend .switcher-wrapper:has(input:nth-of-type(3):checked)~.revealed-items.item3,
body.bricks-is-frontend .switcher-wrapper:has(input:nth-of-type(4):checked)~.revealed-items.item4 {
   display: flex !important;
   animation: 300ms activeItems ease-in-out;
}*/
body.bricks-is-frontend .revealed-items.active{
  display: flex !important;
  animation: 300ms activeItems ease-in-out;
}
// Load the script when the DOM is loaded
window.addEventListener('DOMContentLoaded', () => {
   // Query all the switchers on the page
   const radioGroups = document.querySelectorAll('.switcher-wrapper');
   // stop the script if there is no switcher
   if (radioGroups.length < 1) return;
   // Function to get all the siblings of an element
   const getSiblings = (elem) => {
      return Array.prototype.filter.call(elem.parentNode.children, (sibling) => {
         return sibling !== elem;
      });
   };
   // Loop in each switcher groups
   radioGroups.forEach((radioGroup) => {
      // Query the inputs
      const radios = radioGroup.querySelectorAll('input');
      // Query the revealed items
      const revealedItems = getSiblings(radioGroup);
      // Loop into each radio groups
      radios.forEach((radio, index) => {
         // Listener on click
         radio.addEventListener('click', () => {
            // Get the revealed item
            const revealedItem = revealedItems[index];
            // Remove the active class on all revealed items
            revealedItems.forEach(el => el.classList.remove('active'));
            // Add the active class to the active revealed item
            revealedItem.classList.add('active');
         })
      })
   })
})

💡 译者注:Firefox 从 121 版(2023 年 12 月)起已原生支持 :has(),现在的 Firefox 不再需要这段降级代码——保留它主要兼容旧版浏览器。

相关阅读:

适用场景与方案取舍

纯 CSS 切换器适合内容固定、样式统一的场景:价格表按月/年切换、功能特性分组、FAQ 标签页这类「切换的是内容块、不涉及数据请求」的需求,零 JS、零插件依赖,首屏性能最干净。

反过来,如果切换内容来自后台数据、需要异步加载,或者要联动其它元素(比如切换时改变图表、联动筛选),那用 BricksExtras 的集成方案或 JS 更合适。取舍标准就一条:切换的是静态内容还是动态逻辑——静态走 CSS,动态上插件/JS。

结论

设置正确的话,前端应该能看到:点击标签 → 高亮滑块平滑滑动 → 对应内容块淡入显示。

需要提醒:这个方案开箱即用不满足无障碍要求(accessibility)。如果你的项目强制要求无障碍,请换用其他方案,或自行补充额外工作。

常见问题(FAQ)

Q:最多支持几个标签? A:这套 CSS 原生支持到 4 个标签项,示例用的是 3 个。想增减就按需增删 HTML 条目,同时记得把 CSS 变量 --switch-item-number 改成实际数量。

Q:怎么改默认选中的标签? A:默认选中哪个,就把 checked 属性加到哪个 <input> 上(示例默认选中第二个)。

Q::has() 兼容性怎么样? A:Firefox 121 起(2023 年 12 月)已原生支持 :has(),现代浏览器都没问题;旧版浏览器需要文中的降级方案(.active 类 + 一段 JS)。

Q:移动端怎么竖排? A:文中有现成的媒体查询 CSS,max-width: 767px 下自动变为竖排布局,直接追加即可。

Q:这个方案有坑吗? A:开箱即用不满足无障碍(accessibility)要求,强制无障碍的项目请换方案或自行补足。

延伸阅读

Ray Chan

站长

Ray Chan

WordPress Developer & Bricks Specialist

WordPress developer with 10+ years of client builds. Switched to Bricks in 2023 — now builds fast WordPress sites and migrates legacy Elementor/Divi projects.

延伸阅读