首页 / 中文教程 / 教程

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

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

Ray Chan·2026-08-12·约 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 不再需要这段降级代码——保留它主要兼容旧版浏览器。

结论

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

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

需要帮忙?

用 Bricks 建站?我接客户项目。

从快速营销站到完整的 Bricks 建站,再到从 Elementor 迁移——我都做过。本站每篇教程都来自真实项目经验。告诉我你的需求,一个工作日内回复。

  • Bricks 建站与改版
  • Elementor / Divi → Bricks 迁移
  • Bricks → Astro / headless 性能升级
  • 速度优化,PageSpeed 95+ 目标