Interactive

Vote

Upvote/downvote component for rating content. Works with default arrows, enhances with custom icons or layouts.

Example

42
127
-8
<div class="omni-vote">
  <button class="omni-vote-button" data-action="upvote" aria-pressed="false" aria-label="Upvote"></button>
  <div class="omni-vote-score">42</div>
  <button class="omni-vote-button" data-action="downvote" aria-pressed="false" aria-label="Downvote"></button>
</div>

Layouts

Vertical (Default)

Default layout stacks upvote, score, and downvote vertically:

42
<div class="omni-vote">
  <button class="omni-vote-button" data-action="upvote" aria-pressed="false" aria-label="Upvote"></button>
  <div class="omni-vote-score">42</div>
  <button class="omni-vote-button" data-action="downvote" aria-pressed="false" aria-label="Downvote"></button>
</div>

Horizontal

Use data-layout="horizontal" for inline voting (e.g., in comments):

42
<div class="omni-vote" data-layout="horizontal">
  <button class="omni-vote-button" data-action="upvote" aria-pressed="false" aria-label="Upvote"></button>
  <div class="omni-vote-score">42</div>
  <button class="omni-vote-button" data-action="downvote" aria-pressed="false" aria-label="Downvote"></button>
</div>

States

Active States

Use aria-pressed="true" to indicate active vote state:

128
-5
<!-- Upvoted -->
<div class="omni-vote">
  <button class="omni-vote-button" data-action="upvote" aria-pressed="true" aria-label="Upvote (active)"></button>
  <div class="omni-vote-score">128</div>
  <button class="omni-vote-button" data-action="downvote" aria-pressed="false" aria-label="Downvote"></button>
</div>

<!-- Downvoted -->
<div class="omni-vote">
  <button class="omni-vote-button" data-action="upvote" aria-pressed="false" aria-label="Upvote"></button>
  <div class="omni-vote-score">-5</div>
  <button class="omni-vote-button" data-action="downvote" aria-pressed="true" aria-label="Downvote (active)"></button>
</div>

Disabled

Use disabled attribute to prevent voting:

99
<div class="omni-vote">
  <button class="omni-vote-button" data-action="upvote" aria-pressed="false" aria-label="Upvote" disabled></button>
  <div class="omni-vote-score">99</div>
  <button class="omni-vote-button" data-action="downvote" aria-pressed="false" aria-label="Downvote" disabled></button>
</div>

Custom Icons

Replace default arrows with SVG icons or alternative Unicode characters:

SVG Icons

42
<div class="omni-vote">
  <button class="omni-vote-button" data-action="upvote" aria-pressed="false" aria-label="Upvote">
    <svg viewBox="0 0 24 24" fill="currentColor" width="16" height="16">
      <path d="M12 4l8 8h-6v8h-4v-8H4z"/>
    </svg>
  </button>
  <div class="omni-vote-score">42</div>
  <button class="omni-vote-button" data-action="downvote" aria-pressed="false" aria-label="Downvote">
    <svg viewBox="0 0 24 24" fill="currentColor" width="16" height="16">
      <path d="M12 20l-8-8h6V4h4v8h6z"/>
    </svg>
  </button>
</div>

Alternative Unicode

Use alternative arrow characters like ▴▾, △▽, or ⌃⌄:

42
42
42
<!-- Alternative arrows: ▴▾ -->
<div class="omni-vote">
  <button class="omni-vote-button" data-action="upvote" aria-pressed="false" aria-label="Upvote">▴</button>
  <div class="omni-vote-score">42</div>
  <button class="omni-vote-button" data-action="downvote" aria-pressed="false" aria-label="Downvote">▾</button>
</div>

Real-World Examples

Forum Post

47

How do I implement progressive enhancement?

Start with CSS-only components, then enhance with JavaScript where needed...

Comment Thread

User avatar
Sarah Chen 2 hours ago

This is exactly what I needed! The data attribute system makes it so easy to customize.

12

API Reference

Attribute Values Default Description
data-layout vertical, horizontal vertical Layout direction (on .omni-vote)
data-action upvote, downvote Button action (on .omni-vote-button)
aria-pressed true, false false Active vote state (on buttons)
aria-label string Required accessible label (on buttons)
disabled boolean Prevents interaction (on buttons)

CSS Custom Properties

Property Default Description
--omni-variant-gap var(--omni-space-1) Gap between elements
--omni-variant-button-size 2rem Button width and height
--omni-variant-radius var(--omni-radius-sm) Button border radius
--omni-variant-score-padding var(--omni-space-1) Score horizontal padding
--omni-variant-font-size var(--omni-font-sm) Score font size

Accessibility

  • Always include aria-label on vote buttons for screen reader users
  • Use aria-pressed to indicate active vote state (toggled button pattern)
  • Full keyboard navigation support (Tab, Enter, Space)
  • Focus visible indicators for keyboard users
  • Active states use semantic colors (green for upvote, red for downvote)
  • Disabled state prevents interaction and conveys unavailability visually
  • Score is readable text, accessible to screen readers and text selection
  • Default arrows (▲▼) work without custom fonts or icons

Components Used

Other OmniUI components demonstrated on this page: