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
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-labelon vote buttons for screen reader users - Use
aria-pressedto 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: