Skip to content
RTL Support: Full
Accessibility: Work in progress
Translations: Not Needed

Accordion

A collapsible content panel that can be used to show and hide content in an organized way. Content padding to be customized by the dev.

Default

A basic accordion where expanding one panel collapses the others.

Title 1
Title 2
vue
<template>
  <HLAccordion>
    <HLAccordionItem title="Title 1" name="1" id="1">
      <div class="p-2">Content for section 1</div>
    </HLAccordionItem>
    <HLAccordionItem title="Title 2" name="2" id="2">
      <div class="p-2">Content for section 2</div>
    </HLAccordionItem>
  </HLAccordion>
</template>
<script setup lang="ts">
  import { HLAccordion, HLAccordionItem } from '@platform-ui/highrise'
</script>

Sizes

Use size to render the accordion in small, medium (default), or large dimensions.

Small Size
Medium Size
Large Size
vue
<template>
  <!-- Small Size -->
  <HLAccordion size="sm">
    <HLAccordionItem title="Small Size" name="1" id="1">
      <div class="p-2">Small accordion content</div>
    </HLAccordionItem>
  </HLAccordion>

  <!-- Medium Size (default) -->
  <HLAccordion size="md">
    <HLAccordionItem title="Medium Size" name="2" id="2">
      <div class="p-2">Medium accordion content</div>
    </HLAccordionItem>
  </HLAccordion>

  <!-- Large Size -->
  <HLAccordion size="lg">
    <HLAccordionItem title="Large Size" name="3" id="3">
      <div class="p-2">Large accordion content</div>
    </HLAccordionItem>
  </HLAccordion>
</template>
<script setup lang="ts">
  import { HLAccordion, HLAccordionItem } from '@platform-ui/highrise'
</script>

Arrow Placement

Use arrowPlacement to position the expand/collapse arrow on the left or right.

Left Arrow
Right Arrow
vue
<template>
  <!-- Left Arrow -->
  <HLAccordion arrowPlacement="left">
    <HLAccordionItem title="Left Arrow" name="1" id="1">
      <div class="p-2">Content with left arrow</div>
    </HLAccordionItem>
  </HLAccordion>

  <!-- Right Arrow -->
  <HLAccordion arrowPlacement="right">
    <HLAccordionItem title="Right Arrow" name="2" id="2">
      <div class="p-2">Content with right arrow</div>
    </HLAccordionItem>
  </HLAccordion>
</template>
<script setup lang="ts">
  import { HLAccordion, HLAccordionItem } from '@platform-ui/highrise'
</script>

Border Styles

Use borderPosition to draw the border around the whole accordion (default) or around each item (item).

Default Border
Default Border
Item Border
Item Border
vue
<template>
  <!-- Default Border -->
  <HLAccordion borderPosition="default">
    <HLAccordionItem title="Default Border" name="1" id="1">
      <div class="p-2">Content with default border - item 1</div>
    </HLAccordionItem>
    <HLAccordionItem title="Default Border" name="2" id="2">
      <div class="p-2">Content with default border - item 2</div>
    </HLAccordionItem>
  </HLAccordion>

  <!-- Item Border -->
  <HLAccordion borderPosition="item">
    <HLAccordionItem title="Item Border" name="2" id="2">
      <div class="p-2">Content with item border - item 1</div>
    </HLAccordionItem>
    <HLAccordionItem title="Item Border" name="3" id="3">
      <div class="p-2">Content with item border - item 2</div>
    </HLAccordionItem>
  </HLAccordion>
</template>
<script setup lang="ts">
  import { HLAccordion, HLAccordionItem } from '@platform-ui/highrise'
</script>

No Border

Set :border="false" to render the accordion without any borders.

No Border
No Border 2
vue
<template>
  <HLAccordion :border="false">
    <HLAccordionItem title="No Border" name="1" id="1">
      <div class="p-2">This item has no border</div>
    </HLAccordionItem>
    <HLAccordionItem title="No Border 2" name="2" id="2">
      <div class="p-2">This item has no border</div>
    </HLAccordionItem>
  </HLAccordion>
</template>
<script setup lang="ts">
  import { HLAccordion, HLAccordionItem } from '@platform-ui/highrise'
</script>

Zero Padding

Set zeroPadding on the accordion to strip the built-in header and content padding. The component intentionally leaves spacing to you in this mode, so wrap your content (and any custom header) in your own padded container to control the layout — handy when a panel holds an edge-to-edge element such as a table, list, or image.

Flush content
Content sits flush — no built-in padding
Custom padding
Content with developer-controlled padding
vue
<template>
  <!-- Without your own padding the content sits flush against the edges -->
  <HLAccordion zeroPadding :defaultExpandedNames="['flush']">
    <HLAccordionItem title="Flush content" name="flush" id="1">
      <div>Content sits flush — no built-in padding</div>
    </HLAccordionItem>
  </HLAccordion>

  <!-- Add your own wrapper to control the spacing -->
  <HLAccordion zeroPadding :defaultExpandedNames="['custom']">
    <HLAccordionItem title="Custom padding" name="custom" id="2">
      <div class="px-4 py-3">Content with developer-controlled padding</div>
    </HLAccordionItem>
  </HLAccordion>
</template>
<script setup lang="ts">
  import { HLAccordion, HLAccordionItem } from '@platform-ui/highrise'
</script>

Disabled

Set disabled on an item to prevent it from being expanded.

Enabled Item
Disabled Item
vue
<template>
  <HLAccordion>
    <HLAccordionItem title="Enabled Item" name="1" id="1">
      <div>This item can be expanded</div>
    </HLAccordionItem>
    <HLAccordionItem title="Disabled Item" name="2" id="2" :disabled="true">
      <div>This item cannot be expanded</div>
    </HLAccordionItem>
  </HLAccordion>
</template>
<script setup lang="ts">
  import { HLAccordion, HLAccordionItem } from '@platform-ui/highrise'
</script>

Hover Effect

hoverEffect is set on each HLAccordionItem and highlights the item's header background on hover. It is enabled by default (true); set :hoverEffect="false" to turn the highlight off.

Hover me (effect on)
Hover me (effect off)
vue
<template>
  <HLAccordion>
    <!-- hoverEffect defaults to true -->
    <HLAccordionItem title="Hover me (effect on)" name="1" id="hover-on">
      <div class="p-2">Header highlights on hover — the default.</div>
    </HLAccordionItem>
    <HLAccordionItem title="Hover me (effect off)" name="2" id="hover-off" :hoverEffect="false">
      <div class="p-2">No hover highlight on this item.</div>
    </HLAccordionItem>
  </HLAccordion>
</template>
<script setup lang="ts">
  import { HLAccordion, HLAccordionItem } from '@platform-ui/highrise'
</script>

Multiple Open Panels

Setting the accordion prop to false will allow opening multiple panels at the same time.

Expandable Item
Expandable Item
Expandable Item
vue
<template>
  <HLAccordion id="multiple-panels-open" :accordion="false">
    <HLAccordionItem title="Expandable Item" name="1" id="1">
      <div class="p-2">This section can be expanded</div>
    </HLAccordionItem>
    <HLAccordionItem title="Expandable Item" name="2" id="2">
      <div class="p-2">This section can be expanded irrespective of the other sections</div>
    </HLAccordionItem>
    <HLAccordionItem title="Expandable Item" name="3" id="3">
      <div class="p-2">This section can be expanded irrespective of the other sections</div>
    </HLAccordionItem>
  </HLAccordion>
</template>
<script setup lang="ts">
  import { HLAccordion, HLAccordionItem } from '@platform-ui/highrise'
</script>

Pre-expanded Panels

Use defaultExpandedNames to set which panels start open on first render.

defaultExpandedNames vs expandedNames

  • defaultExpandedNamesuncontrolled. Sets the initial open panels only; the user can freely collapse and expand them afterwards. The accordion manages its own state from then on.
  • expandedNamescontrolled. Forces the listed panels open and keeps them open — the user cannot collapse them unless you update the bound value yourself. Use this when the expanded state is driven by your own application state.

Pass one or the other, not both.

Pre-expanded Item
This section starts expanded
Collapsed Item
Pre-expanded Item 1
This section starts expanded
Pre-expanded Item 2
This section also starts expanded
vue
<template>
  <!-- Single pre-expanded panel -->
  <HLAccordion :defaultExpandedNames="['first']">
    <HLAccordionItem title="Pre-expanded Item" name="first" id="1">
      <div class="p-2">This section starts expanded</div>
    </HLAccordionItem>
    <HLAccordionItem title="Collapsed Item" name="second" id="2">
      <div class="p-2">This section starts collapsed</div>
    </HLAccordionItem>
  </HLAccordion>
  <!-- Multiple pre-expanded panels -->
  <HLAccordion :defaultExpandedNames="['first', 'second']" :accordion="false">
    <HLAccordionItem title="Pre-expanded Item 1" name="first" id="3">
      <div class="p-2">This section starts expanded</div>
    </HLAccordionItem>
    <HLAccordionItem title="Pre-expanded Item 2" name="second" id="4">
      <div class="p-2">This section also starts expanded</div>
    </HLAccordionItem>
  </HLAccordion>
</template>
<script setup lang="ts">
  import { HLAccordion, HLAccordionItem } from '@platform-ui/highrise'
</script>

Controlled State

When you drive the expanded panels from your own reactive state, pass expandedNames (controlled) and handle @item-header-click to update it. On its own expandedNames forces the listed panels open and the user cannot collapse them — the click handler is what lets a click toggle the state.

Section 1
Content for section 1
Section 2
Section 3

Open panels: 1

vue
<template>
  <HLAccordion
    :accordion="false"
    :expandedNames="expandedNames"
    @item-header-click="handleToggle"
  >
    <HLAccordionItem title="Section 1" name="1" id="ctrl-1">
      <div class="p-2">Content for section 1</div>
    </HLAccordionItem>
    <HLAccordionItem title="Section 2" name="2" id="ctrl-2">
      <div class="p-2">Content for section 2</div>
    </HLAccordionItem>
    <HLAccordionItem title="Section 3" name="3" id="ctrl-3">
      <div class="p-2">Content for section 3</div>
    </HLAccordionItem>
  </HLAccordion>
</template>
<script setup lang="ts">
  import { ref } from 'vue'
  import { HLAccordion, HLAccordionItem } from '@platform-ui/highrise'

  const expandedNames = ref<Array<string | number>>(['1'])

  // Toggle the clicked panel in our own state.
  // Without this handler the panels stay locked open.
  const handleToggle = ({ name }: { name: string | number }) => {
    expandedNames.value = expandedNames.value.includes(name)
      ? expandedNames.value.filter(n => n !== name)
      : [...expandedNames.value, name]
  }
</script>

Dynamic Items

Render items from an array with v-for. Give each item a stable, unique :key, :id, and :namename is the value that shows up in @item-header-click and is matched against expandedNames / defaultExpandedNames, so it must be unique within the accordion.

General
Notifications
Billing
vue
<template>
  <HLAccordion :accordion="false">
    <HLAccordionItem
      v-for="section in sections"
      :key="section.key"
      :id="`section-${section.key}`"
      :name="section.key"
      :title="section.title"
    >
      <div class="p-2">{{ section.body }}</div>
    </HLAccordionItem>
  </HLAccordion>
</template>
<script setup lang="ts">
  import { ref } from 'vue'
  import { HLAccordion, HLAccordionItem } from '@platform-ui/highrise'

  const sections = ref([
    { key: 'general', title: 'General', body: 'General settings live here.' },
    { key: 'notifications', title: 'Notifications', body: 'Notification preferences live here.' },
    { key: 'billing', title: 'Billing', body: 'Billing details live here.' },
  ])
</script>

Custom Title

Both slots used here belong to HLAccordionItem, not HLAccordion. Use the item's header slot to replace the plain title prop with custom markup — headings, icons, badges, or any component — and the item's header-extra slot to render content beside the expand/collapse arrow (for example a status tag or a secondary action). When you provide a header slot, it takes the place of the title prop, so you don't need to pass title as well.

Header slot

Header Extra
vue
<template>
  <HLAccordion>
    <HLAccordionItem name="1" id="1">
      <template #header>
        <div class="p-2 bg-green-50 w-full">
          <HLText size="xl" weight="semibold">Header slot</HLText>
        </div>
      </template>
      <template #header-extra>
        <div class="p-2 bg-gray-50 w-full">Header Extra</div>
      </template>
      <div class="p-2">Content for section 1</div>
    </HLAccordionItem>
  </HLAccordion>
</template>
<script setup lang="ts">
  import { HLAccordion, HLAccordionItem, HLText } from '@platform-ui/highrise'
</script>

Custom Content

The panel body is the default slot of HLAccordionItem — the content shown when that item is expanded. It accepts any markup, so a panel can hold rich content like images, text, and actions. Note this is different from the default slot of HLAccordion, which holds the HLAccordionItem children themselves: the accordion's default slot defines which panels exist, while each item's default slot defines what that panel contains.

Custom Content Example
vue
<template>
  <!-- Custom content -->
  <HLAccordion>
    <HLAccordionItem title="Rich Content Example" name="2" id="2">
      <div class="p-2">
        <div class="flex gap-4">
          <div class="w-[100px] h-[100px] bg-[#D9D9D9] rounded"></div>
          <div>
            <HLText size="lg" weight="semibold">Content Title</HLText>
            <p>This is an example of rich content with image placeholder and text.</p>
            <HLButton color="blue" variant="primary" size="sm">Action Button</HLButton>
          </div>
        </div>
      </div>
    </HLAccordionItem>
  </HLAccordion>
</template>
<script setup lang="ts">
  import { HLAccordion, HLAccordionItem, HLText, HLButton } from '@platform-ui/highrise'
</script>

Display Directive

The displayDirective property specifies which Vue directive to use for controlling the visibility of the accordion's content. This choice affects how the content is managed in the DOM, impacting both performance and behavior.

if - When the accordion is collapsed, the content is completely removed from the DOM. It is only added back when the accordion is expanded. This approach is more performant when the content is not needed initially.

show - The content remains in the DOM at all times, but it is hidden from view when the accordion is collapsed. The visibility is toggled using CSS. This approach is more flexible and easier to manage when the content needs to be toggled on and off.

Event Testing

This example shows handling of the @item-header-click event. Click on the header of an accordion item to trigger the @item-header-click event.

Item 1
Item 2

Event Log:

No events logged yet. Try clicking the headers above.
vue
<script setup>
  import { ref } from 'vue'
  import { HLAccordion, HLAccordionItem } from '@platform-ui/highrise'

  const eventLog = ref([])
  const handleHeaderClick = item => {
    eventLog.value.unshift({
      event: 'Header clicked',
      expanded: item.expanded,
      name: item.name,
      timestamp: new Date().toLocaleTimeString(),
    })
    // Keep only last 5 events
    if (eventLog.value.length > 5) {
      eventLog.value.pop()
    }
  }
</script>

<template>
  <HLAccordion @item-header-click="handleHeaderClick">
    <HLAccordionItem title="Item 1" name="1" id="1">
      <div class="p-2">Content for item 1</div>
    </HLAccordionItem>
    <HLAccordionItem title="Item 2" name="2" id="2">
      <div class="p-2">Content for item 2</div>
    </HLAccordionItem>
  </HLAccordion>
  <div class="text-sm">
    <p class="font-bold mb-2">Event Log:</p>
    <div v-if="eventLog.length === 0" class="text-gray-500">No events logged yet. Try clicking the headers above.</div>
    <div v-for="(log, index) in eventLog" :key="index" class="text-gray-700">
      {{ log.timestamp }}: {{ log.event }} - Expanded: {{ log.expanded }} - Name: {{ log.name }}
    </div>
  </div>
</template>

Accessibility

  • Each HLAccordionItem header is rendered as a real <button> with aria-expanded, aria-controls, and role="region" behavior. Give every item a unique id and human-readable title so those generated attributes read clearly for assistive tech.
  • Make sure to handle aria-* attributes for elements passed to the slots

Imports

ts
import { HLAccordion, HLAccordionItem } from '@platform-ui/highrise'

Props

Accordion

NameTypeDefaultDescription
id *string | undefinedundefinedUnique identifier for the accordion
accordionbooleantrueWhen set to true, the accordian operates in a traditional manner where only one panel can be open at a time, opening one will close the other. When set to false, the accordian will allow multiple panels to be open at the same time.
arrowPlacement'left' | 'right''right'Position of the expand/collapse arrow
size'sm' | 'md' | 'lg''md'Size of the accordion
borderPosition'default' | 'item''default'Position of the border
borderbooleantrueDisplay border
defaultExpandedNamesstring[] | string | undefinedundefinedPre-expanded panels that can be collapsed
displayDirective'if' | 'show''if'Vue directive to use for content display
expandedNamesstring[] | string | undefinedundefinedExpanded panels that cannot be collapsed
zeroPaddingbooleanfalseRemove padding from accordion items

AccordionItem

NameTypeDefaultDescription
id *string | undefinedundefinedUnique identifier for the accordion item
titlestring | undefinedundefinedTitle of the accordion item
namestring | undefinedundefinedName of the accordion item
disabledbooleanfalseDisable the accordion item
hoverEffectbooleantrueHighlight the item's header on hover

Emits

NameArgumentsDescription
@item-header-click(item: { name: string | number, expanded: boolean, event: MouseEvent }) => voidTriggered when an item header is clicked. expanded reflects the item's state after the click.

Slots

Accordion

NameParametersDescription
default()Content for the accordion items

AccordionItem

NameParametersDescription
default()Content for the accordion item
header()Content for the accordion header
header-extra()Content for the extra header beside arrow