Button
The most-tapped component in any app. Learn the emphasis levels, the six states every button has, and the bugs that create double orders and endless spinners.
You tap Place order in a food delivery app. Nothing seems to happen, so you tap again. Ten minutes later two pizzas arrive and your card was charged twice. That’s a buttonButtonA control the user taps or clicks to perform an action, like Save, Send or Buy.Open in glossary → bug, and it’s one of the most expensive bugs a team can ship.
Buttons look like the simplest thing on the screen. But every button has several states, talks to a server, and has to work with touch, mouse, keyboard and screen readers. That’s a lot of places for bugs to hide.
What is a button?
A button is a control that starts an action when you tap or click it: pay, send, save, delete, add to basket. The label tells you what will happen, and the look tells you how important the action is.
You’ll see buttons used to:
- Commit an action: Place order, Send money, Book flight.
- Move forward in a flow: Continue, Next, Sign up.
- Do something small in place: Like, Share, Add another item.
- Destroy something: Delete account, Remove card, Cancel subscription.
Anatomy
- 1
Container
The shape and fill. Its color and style signal how important the action is.
- 2
Label
A short verb that says exactly what happens: "Add to basket", not "OK".
- 3
Icon (optional)
Helps scanning. Never the only cue unless the icon is universally understood.
- 4
Touch target
The tappable area. It can be larger than the visible shape: at least 44×44 pt (iOS) or 48×48 dp (Android).
How it differs from similar components
A common finding is a button that should have been something else, or something else pretending to be a button.
| Component | What happens on tap | Keeps a state? | Typical use |
|---|---|---|---|
| Button | Runs an action | No (it doesn’t stay “on”) | Pay, Save, Send, Delete |
| Link | Navigates to another page or screen | No | “View order details”, “Terms of service” |
| Toggle switch | Turns a setting on or off immediately | Yes, on/off | Notifications, Wi-Fi |
| Segmented control | Picks one of a few views | Yes, one selected | List / Map |
| Chip | Filters or adds a tag | Often | “Vegan”, “Under €20” |
| Menu item | Runs an action from a list | No | Overflow menu: Rename, Duplicate |
Designs & variants
Buttons come in emphasis levels. The more important the action, the louder the button. Each platform has its own names for them.
Pick a platform, then hover, press and Tab through the buttons. They all respond.
Primary · Filled
High emphasisThe single most important action on the screen: Pay, Place order, Continue. Usually one per screen.
Secondary · Tinted
Medium emphasisImportant, but not the main path: Save for later, Add another item. Softer fill, same shape.
Secondary · Outline (custom)
Medium emphasisOutline only. Common on the web for Cancel or Back next to a primary button.
Tertiary · Plain
Low emphasisText only: Skip, Learn more, Forgot password? It still needs a full-size touch target, even without a visible shape.
Destructive
AllRed for actions that delete or can't be undone. It should almost always lead to a confirmation or an Undo.
Icon buttons
AllSave space in toolbars. Each one needs an accessible name, or a screen reader just says 'button'.
Sizes
iOSSmall, medium and large. Small buttons still need a 44 pt / 48 dp touch target around them.
Floating action button (FAB)
Android · Material 3Floats above content for the screen's main action, like New order or Compose. iOS has no FAB; it uses a toolbar button instead.
Trail running shoes
Size 42 · Ocean blue
Total
€89.90
Sticky / floating CTA
AllA call-to-action pinned to the bottom of the screen. Check it never hides content, the keyboard, or the home indicator.
Button pair
iOSPrimary + secondary side by side. Check the order follows the platform (web dialogs often put the primary on the right).
| Emphasis | iOS | Android (Material 3) | Web (typical) | Use it for |
|---|---|---|---|---|
| High | Filled (.borderedProminent) | Filled | Primary | The main action. One per screen. |
| Medium | Tinted / Gray (.bordered) | Filled tonal, Outlined | Secondary | Alternatives: Save for later, Cancel |
| Low | Plain (.borderless) | Text | Tertiary, link-style | Skip, Learn more, Forgot password? |
| Destructive | role: .destructive (red) | Error color (custom) | Danger | Delete, Remove, Cancel subscription |
| Floating | Not used | FAB, extended FAB | Rare | The screen’s main creation action |
Six states, every time
A button is not one picture. It’s six. Designers often deliver only the default look, so developers invent the rest, and that’s where bugs appear. Explore them here:
🎛️ Button states explorer
Every button has six states. A tester checks all of them on every platform and in both themes.
Platform
Emphasis
Default
Readable label, enough contrast (4.5:1 text, 3:1 shape against the background).
Hover
Web and iPad/desktop pointers only. A subtle change; never the only hint that something is clickable.
Pressed
Instant feedback on touch-down, so the user knows the tap registered.
Focused
Keyboard users must see a clear focus ring. Press Tab on the web and look for it.
Disabled
Looks inactive and really is: no tap, no click, no Enter key, no request.
Loading
Shows progress, blocks repeat taps, keeps its size, and always ends (success or error).
Now try a real one
Server response
Tap the button several times quickly while it loads. A good button counts your taps but sends only one request.
Ready
When to use it
A button is the right choice when:
- Tapping it performs an action (saves, sends, pays, deletes) rather than just showing another page.
- The action has a clear verb you can put on the label.
- There is a clear hierarchy: one primary action, maybe a secondary one, and the rest as low-emphasis buttons.
- The action needs feedback: a loading state, a success message or an error.
Good and bad uses
Button for an action, link for navigation
Placing an order does something. Reading the terms goes somewhere.
Buttons that only navigate
“Click here” says nothing, and navigation dressed up as a button confuses screen reader users and breaks Ctrl/Cmd-click on the web.
One clear primary action
The eye goes straight to the main action.
Everything shouts
Three primary buttons compete. Users hesitate or tap the wrong one.
Specific verb labels
You know exactly what each button does, even without reading the dialog text.
Vague labels
“Remove this card? Cancel / OK”. Does Cancel cancel the removal or remove the card? Users have to guess.
Big enough to hit
Tap area: 44×44 pt. The icon is small, the target is not.
The visible icon can be small, but the touch area must be at least 44×44 pt (iOS) or 48×48 dp (Android).
Tiny tap area
Tap area: 18×18 px. Only the icon itself reacts.
Users tap next to it, nothing happens, and they think the app is frozen.
Common bugs
These are the button bugs testers report again and again. Many of them cost real money.
| # | Bug | What you see | Typical severity |
|---|---|---|---|
| 1 | Double submit | Two quick taps create two orders, two payments or two messages | Critical |
| 2 | Endless spinner | After a network error the button keeps loading forever, with no message and no retry | High |
| 3 | Disabled button still works | A greyed-out button can still be tapped, clicked or triggered with Enter | High |
| 4 | Never enabled | The button stays disabled even after the form is valid, often after autofill or paste | High |
| 5 | Label truncated | “Place ord…” or the price cut off in other languages or with large text | Medium |
| 6 | Hidden CTA | The sticky button sits under the keyboard, the home indicator or a cookie banner | High |
| 7 | No confirmation for destructive actions | “Delete account” or “Clear basket” happens instantly with no confirm or undo | High |
| 8 | Tap area too small | Only the icon reacts, not the whole visible button. Users miss it | Medium |
| 9 | No pressed or focus feedback | Nothing changes on tap, or keyboard users can’t see which button is focused | Medium |
| 10 | Layout jump on loading | The button shrinks to the spinner’s size, so the layout jumps and the next tap lands on something else | Low–Medium |
Find the bug
Here’s a food delivery checkout with six bugs. Test it like a real user on a bad day:
- Tap buttons once, then tap them twice quickly.
- Turn on Server errors and try to order.
- Try buttons that look disabled.
- Change the device settings, including the developer option.
- Watch the network log and the order counter.
🐞 Bug hunt: Checkout screen
This demo has 6 hidden bugs. Explore it like a tester, then check your findings.
Your basket
- 🍕Margherita pizza€11.501
- 🥖Garlic bread€4.901
- 🍋Lemonade€7.002
Promo code
Payment
Subtotal€23.40
Delivery€1.50
Device settings
- Requests will appear here…
- 1
Hint: Tap Place order twice quickly and watch the order counter.
- 2
Hint: Turn on Server errors, then place an order.
- 3
Hint: Leave the promo code empty and tap Apply.
- 4
Hint: Turn on Large text and read the main button.
- 5
Hint: Imagine your thumb slips onto Clear.
- 6
Hint: Tap the edge of a + circle, then turn on Show layout bounds.
How to test it
Checklist
Button test checklist
0 of 14 checked
Example test cases
TC-BTN-001Placing an order creates exactly one orderpositivehigh
Preconditions
- User is logged in
- Basket contains at least one item
- Valid payment method is saved
Steps
- Open Checkout
- Tap "Place order"
Expected result
The button shows a loading state, then the "Order placed" screen appears. Exactly one order is visible in Order history.
TC-BTN-002Rapid double tap does not create a duplicate orderedgehigh
Preconditions
- Network throttled to a slow 3G profile
- Proxy tool (e.g. Charles) is recording traffic
Steps
- Open Checkout
- Tap "Place order" twice within 1 second
Expected result
Only one POST /api/orders request is sent. One order is created and the card is charged once.
TC-BTN-003Server error ends the loading state with a messagenegativehigh
Preconditions
- Backend returns 500 for POST /api/orders (mock or test environment)
Steps
- Open Checkout
- Tap "Place order"
- Wait for the response
Expected result
The spinner stops, an error message explains the problem, and "Place order" can be tapped again. No order is created.
TC-BTN-004Disabled button cannot be triggerednegativehigh
Preconditions
- Promo code field is empty
Steps
- Tap "Apply"
- Web: Tab to "Apply" and press Enter and Space
Expected result
Nothing happens: no request is sent and no message appears. Screen reader announces "Apply, dimmed" (iOS) or "disabled".
TC-BTN-005Destructive action asks for confirmationnegativemedium
Steps
- Add 3 items to the basket
- Tap "Clear"
Expected result
A confirmation ("Remove all 3 items?") with a clearly labeled destructive option appears, or an Undo snackbar is shown. Items are not lost by one accidental tap.
TC-BTN-006Label is readable at the largest text sizeedgemedium
Preconditions
- iOS: Settings › Accessibility › Larger Text at maximum / Android: Font size at maximum
Steps
- Open Checkout
- Read the "Place order" button
Expected result
The full label, including the price, is readable. The button may grow taller or wrap the text.
TC-BTN-007Sticky CTA stays reachable with the keyboard openedgemedium
Steps
- Open Checkout
- Tap the promo code field so the keyboard appears
Expected result
"Place order" moves above the keyboard or the screen can be scrolled to reach it. It is never hidden under the keyboard.
TC-BTN-008Icon buttons have an accessible namepositivemedium
Preconditions
- VoiceOver / TalkBack is on
Steps
- Swipe to the Share icon button
- Swipe to the Close (×) icon button
Expected result
They are announced as "Share, button" and "Close, button", not just "button".
Tip: click a test case to expand it.
Accessibility
Buttons are where many users first get stuck. Check these:
- Role and name: Screen readersScreen readerSoftware that reads the screen aloud for blind and low-vision users, like VoiceOver or TalkBack.Open in glossary → must announce the label and the word “button”. A clickable
divhas no role, can’t be focused with Tab, and ignores Enter and Space.Dev note
On the web, use a real<button>element. - Icon-only buttons need an accessible name.
Dev note
accessibilityLabelon iOS,contentDescriptionon Android,aria-labelon the web. - Disabled and busy states must be announced: “dimmed” on iOS, “disabled” on Android and the web. While loading, a live message (“Processing payment”) tells the user something is happening.
Dev note
Oraria-busy="true". - Touch targetsTouch targetThe area of the screen that responds to a tap, which should be large enough for a finger.Open in glossary →: at least 44×44 pt on iOS and 48×48 dp on Android. On the web, WCAG 2.2 asks for at least 24×24 CSS px (2.5.8, level AA); 44×44 CSS px (2.5.5, level AAA) is the comfortable target.
- Contrast: label text at least 4.5:1 against the button, and the button’s shape at least 3:1 against the background. Disabled buttons are exempt, but they must still be recognizable.
- Focus: on the web, every button is reachable with Tab, has a visible focus ring, and works with both Enter and Space.
- Don’t rely on color alone for destructive actions. Red plus a clear label (“Delete account”) works for color-blind users too.
Mobile vs web
Dev note
UIButton with UIButton.Configuration (filled, tinted, gray, plain) or SwiftUI Button with .borderedProminent, .bordered, .borderless.- Pressed state is a quick dim/highlight, not a ripple.
- Destructive actions are red, often inside an action sheet or alert.
Dev note
They userole: .destructive. - No FAB: the main action usually lives in the navigation bar or a bottom toolbar.
- Test with Dynamic Type, VoiceOver, and on small screens like iPhone SE where sticky buttons get tight.
- Material 3 buttons: filled, filled tonal, outlined, text, elevated, plus icon buttons and the FAB.
- Pressed state shows a ripple and a state layer. Disabled = 38% opacity content on a 12% container.
- Watch for the system back gesture during loading: does going back cancel the request or leave a ghost order?
- Device fragmentationFragmentationThe huge variety of device models, screen sizes and OS versions an app must support.Open in glossary → matters: test small and large screens, font scale, display size and TalkBack.
- Developer options › Show layout bounds reveals the real tap areas.
- Use
<button>for actions and<a href>for navigation. Inspect the element in DevToolsDevToolsDeveloper tools built into browsers for inspecting pages, network traffic and errors.Open in glossary → to check. - A button inside a form submits it by default, which causes surprise submits.
Dev note
type="submit"vstype="button"matters. - Test hover, focus and active styles, and pressing Enter in a text field (does it submit twice?).
- Check the button on mobile browsers too: sticky bars vs the browser’s own toolbar and the on-screen keyboard.
- Use DevTools network throttling to test the loading state and double clicks.
Check your understanding
Quick quiz
0/5 answered · 0 correct
1.A user double-taps "Send €50" on a slow connection and Anna receives €100. What should the tester report?
2.Which label is best for the confirm button in a "Remove this card?" dialog?
3.A "Terms of service" control opens a new page. What should it be?
4.After a server error, the "Pay" button keeps spinning forever. Which state was not handled?
5.What is the minimum touch target for a button on Android?
Key takeaways
- A button does something; a link goes somewhere. Pick the right one, and use one primary button per screen.
- Every button has six states: default, hover, pressed, focused, disabled and loading. Test all of them, in light and dark mode.
- The costliest bugs are double submits, endless spinners and disabled buttons that still work. Test with slow and failing networks.
- Labels should be specific verbs that fit at every text size and in every language.
- Check touch targets (44 pt / 48 dp), confirmation for destructive actions, and screen reader announcements.
Finished this page?Nice work, this page is complete!
Mark it as done to track your progress. It's saved in this browser, no account needed.
Its review cards are now in Bites ⚡