The defining feature of a link is the href attribute. Without it, an <a> element is a placeholder for a hyperlink — focusable but not keyboard-activatable, and not announced as a link by screen readers. The value of href can point to a full URL, a relative path, a fragment identifier like #section, or even a protocol like mailto:.

A critical but often-missed detail is the difference between link states: visited, unvisited, and active. Since browsers let users style visited links differently from unvisited ones for privacy reasons, you can only style the :visited state with color-related properties. If you change the font-weight or background-color of a link in its default state, you may discover that the :visited styles won't apply as expected.

Buttons and Button-Like Inputs

Buttons come in two HTML flavors: the <button> element and <input> elements with type="button", type="submit", or type="reset". While both render as clickable controls, the <button> element is more flexible because it can contain structured HTML content, not just text. An <input> is a void element, so its label must live in its value attribute.

The type attribute on a <button> matters more than many realize. The default type for a <button> inside a form is submit. If you place a plain <button> inside a <form> without specifying type="button", clicking it will trigger a form submission — a classic source of frustrating page reloads. The same applies to buttons with a type="reset" value, which will clear the form's fields.

Styling Annoyances and Resets

Buttons inherit a set of default browser styles that can fight your design system. Text in a button is not inherited from the parent by default in all browsers, so you often need to explicitly set font-family and font-size. Similarly, button text may be centered and allow for line wrapping, which differs from the default text-align behavior of other elements.

A common reset involves normalizing box-sizing, removing default padding, and ensuring that button text inherits from its parent. Also, the default focus outline is a keyboard user's best friend; completely removing it with outline: none without providing a replacement is a usability regression.

Accessibility and Semantics

The most robust way to make a clickable element accessible is to use the correct native element. Native links and buttons come with keyboard support, focus management, and the right announcements in assistive technology. Wrapping a div or a span in a click event handler requires you to manually add tabindex="0", handle keydown events for Enter and Space, and implement the correct ARIA role — a set of tasks that is easy to get wrong.

Text never sleeps in accessibility land. The accessible name of a link is calculated from its content. For a button, the accessible name is its text content or its title attribute. Avoid using placeholder text in inputs as a label, as it disappears on input and provides poor cues.

Pitfalls to Avoid

Using a link to trigger a JavaScript function without navigating is semantically dishonest and breaks middle-click and open-in-new-tab behavior. Conversely, using a button to navigate to a page requires JavaScript and loses the ability to open the destination in a new tab. Choose the element that matches the intended behavior of your click target, not the one that looks better in the mockup.

Misuse of # hrefs

Starting an href value with just a # can cause an annoying jump to the top of the page when clicked, if there is no matching id. Better options are to remove the href entirely for a placeholder link, or to use a real button if the intent is to trigger a script.

Space Bar Scroll Prevention

Be careful when capturing keydown events. The Space key normally scrolls the page, so if you attach a click-like behavior to a Space keydown event on a custom element, you need to call preventDefault() to avoid both the scroll and the activation taking place.

When to Pick Which

  • Not sure? When in doubt, a link with a real href is the safest default for moving the user to a new location.
  • Form submission: A <input type="submit" value="Submit"> or a <button type="submit"> works; the latter is more flexible with content.
  • Play/pause and other UI states: These are pure JavaScript actions not tied to navigation, so use a <button>.
  • Component-looking elements: A button copied from a design system that is actually navigating can often be a link styled as a button — lean toward semantic correctness over visual styling.

Both links and buttons are simple to use in a basic sense, but their nuances reveal how much thought front-end development demands at the edge of a click. Getting the element right, filling it with the correct attributes, and defensively styling it comes together to deliver a solid user experience.

Links are the connective tissue of the web. A basic link uses an absolute URL to point to a fully qualified address, while a relative link omits the domain and is useful in development when the production domain isn’t known yet. Relative URLs work well for site navigation, but be cautious using them inside content like blog posts that may be consumed off-site through an app or RSS feed.

Hash links, or jump links, start with # and scroll to the first element in the DOM whose ID matches the hash value. A common pattern is a “back to top” link that sends focus to an element at the top of the page. During development, a hash link like #0 can let you test clicks without jumping the page, but links that go nowhere should never ship to production. Jump links can also target anchor elements that have no href attribute, which are called placeholder links — they have accessibility implications but are generally acceptable.

The only practical way to disable a link is to remove its href attribute entirely. Without it, the element has no role, is not focusable, and receives no keyboard events — functionally it behaves like a <span>. This is useful for links that only become active after a user authenticates.

Opening links in a new tab is strongly discouraged in most cases. The target="_blank" attribute works, but you should also add rel="noopener" for security and performance. Avoid forcing new tabs because you prefer them personally, to inflate time-on-site metrics, or to distinguish internal from external links. Do it only when the user is actively doing something on the current page, such as playing media or has unsaved work, or when you have a genuine technical constraint.

The download attribute on a link tells the browser to download the target file rather than navigate to it, which is a nice touch for file delivery.

The rel Attribute

The rel attribute describes the relationship between the current document and the linked resource. Document-level values include:

  • rel="alternate": Alternate version of the document.
  • rel="author": Author of the document.
  • rel="help": A resource for help with the document.
  • rel="license": License and legal information.
  • rel="manifest": Web App Manifest document.
  • rel="next" / rel="prev": Next or previous document in a series.
  • rel="search": A document that performs a search in the current document.

Search-engine-oriented values include rel="sponsored" for paid placements, rel="ugc" for user-generated content like comments, and rel="nofollow" to tell search engines not to associate the current site with the linked page. Security-focused values include rel="noopener" to block window.opener access from a new tab, and rel="noreferrer" to hide the referring page from the destination’s analytics. Values can be chained with spaces, such as rel="noopener noreferrer".

Several values come from microformats and the indieweb standard, including rel="directory", rel="tag", rel="payment", and rel="me" to indicate the linked destination represents the same person or entity.

Links already have the default ARIA role of link, so there is no need to set it explicitly. Setting the role manually would only be necessary if you were faking a link with non-anchor elements — a practice that breaks right-clicking, new-tab opening, Windows High Contrast Mode, and reader modes, while forcing you to reimplement navigation in JavaScript.

For indicating the current page, use aria-current="page" on the appropriate link rather than inventing your own markers.

The title attribute is generally not worth using on links. It produces a hover-only popup that cannot be styled, is not accessible across touch devices, and provides no benefit over writing clear anchor text or surrounding content. Reserve title for giving an iframe a short, descriptive label.

Icon-only links fail to convey their purpose for both assistive technology and many sighted users. Text labels are almost always clearer. If you must use an icon alone, pair it with a .visually-hidden CSS class that hides the label visually but keeps it in the accessibility tree. Unlike aria-label, visually hidden text is translatable and holds up better in specialized browsing modes.

Wrapping an image in a link is straightforward, and the image’s alt text does not need to state that it is a link — assistive technology already announces that. For larger blocks of content, wrapping an entire card in a link is valid but has implications: text selection becomes difficult, and the full card content is read before the link is announced. An alternative approach is to place the link on the card’s title and use a CSS pseudo-element on the link to make the entire card clickable. That pattern also allows multiple links within the card since you cannot nest links; additional links on top can be layered with z-index.

Links default to an underlined, colored appearance that you will almost certainly override. You can style all links globally with a selector like:

a {
  color: red;
}

That can be heavy-handed since navigation links often need treatment entirely different from links in body text. Scope your selectors to target links within a particular container, or style individual links directly.

Links are focusable and respond to several pseudo-classes:

  • :hover: when the mouse pointer is over the link.
  • :visited: when the link has been followed, subject to browser security limits on styling.
  • :link: when the link has not been visited.
  • :active: when the link is being pressed or tapped.
  • :focus: always give links a visible focus style, especially if you remove the default browser outline.

These pseudo-classes are chainable, so you can combine them when the design demands it. A link can also be styled to look like a button with border, padding, and background — that is fine, but it should be a semantic link if it navigates.

Color Contrast and Print Styles

When styling links with a unique color, check that the color maintains sufficient contrast against its background. Tools like Chrome DevTools can flag non-compliant ratios. Remember that hover, focus, active, and visited states may each use different colors, and text selection compounds the contrast considerations.

For print, CSS includes the @media print at-rule to apply styles only when printing a page. You can also reset link styling entirely with the all property or remove individual styles with keywords like color: initial and text-decoration: none.

Calling event.preventDefault() inside a click handler stops the browser from navigating. This mechanism is central to single-page applications, which intercept navigation, fetch data, update the DOM, and rewrite the URL. It gives page transitions the opportunity to animate, but it is substantial work to replicate what browsers do natively.

Leaving a page via a link can lose unsaved form data. The beforeunload event is the one hook to intercept the user before they leave. When you prevent a link’s default behavior, though, no new destination is announced; you must manually update the page title and move focus back to the top so assistive technology users know where they ended up.

In frameworks like React, custom <Link /> components typically render a real native <a> element with added routing logic and attributes like aria-current="page". Whether you use the abstraction or a plain anchor, the semantic element stays the same.

Link content should be meaningful without the words “link” or “go to.” Avoid using the URL as the display text. Blue underlined links remain a widely understood convention worth keeping. Accessible names must also be unique: if a page has four article cards each linking to “Read More,” a screen reader’s list of interactive elements shows the same unhelpful label four times. Adding visually hidden context or simply using clearer link text removes that ambiguity for everyone.

Choosing the Right Element

When something performs an action rather than navigating, a <button> is the correct element. A useful test: if clicking it has no meaningful href, or if it does nothing without JavaScript, use a button. The only exception to the default behavior rule is inside a <form>, where a button will submit the form unless its type attribute says otherwise.

HTML Implementation

Button elements inside a form default to type="submit", which submits the form. They can also be set to type="reset" to clear it. Additionally, buttons can override attributes of their parent form directly using formaction, formenctype, formmethod, formnovalidate, and formtarget.

The autofocus attribute can bring focus to a button when the page loads. This is usually only appropriate after a user action, such as when a modal dialog opens and a default action should be immediately reachable via the Enter key. Automatically moving focus without permission can be disorienting for screen reader and screen magnifier users. Note that autofocus may not work inside an <iframe sandbox> for security reasons.

To prevent interaction entirely, the disabled attribute is available. If you use it, consider including descriptive text alongside the button so users understand why it is disabled. Often it is better to let users attempt submission and explain what went wrong in the validation feedback instead.

Unlike <input type="submit">, a <button> can contain child elements such as icons or other markup. For a decorative icon inside a button, add focusable="false" and potentially aria-hidden="true" so assistive technology announces only the button’s label.

Styling Buttons

Buttons should be styled to look pressable. Default appearances vary significantly across browsers and platforms. While leaving defaults can provide affordance consistent with a given platform, most design systems require a reset to create consistent cross-browser styles.

Completely removing default button styling is more involved than it seems. While appearance: none; might come to mind, all: revert; is more reliable for wiping the slate clean. Normalize.css demonstrates how many properties are involved in a proper reset.

A common approach is to create a base .button class that provides a consistent foundation across both <a> and <button> elements. This class should account for properties like display, text-decoration, cursor, and border to ensure both elements behave the same way.

Just like links, buttons require styling for their interactive states: :hover, :focus, :active and :disabled. Using ARIA attributes such as aria-pressed for styling is a good way to encourage correct usage of those attributes.

Special Cases

There are legitimate exceptions where a button might look like a link, such as when it triggers a modal dialog instead of navigating away from the page, like opening a user settings panel. In this scenario, a .link-looking-button class that incorporates reset styles and matches anchor link styling is appropriate.

For the opposite scenario, when you want an entire outer element to act as a single clickable area but need a button inside it for semantics, a “breakout” button technique uses an absolutely-positioned pseudo-element on the button to expand its clickable region across the whole container.

JavaScript Behavior

A native <button> element is keyboard operable out of the box: it responds to both Enter and Space keys when focused. This built-in behavior contributes to discoverability and predictability for assistive technology users.

For high-stakes actions like payment submission, attaching a “once” handler prevents duplicate requests. The button should also be disabled on click, making the state visible to the user. You can then intentionally re-enable it and reattach the handler when necessary.

Inline JavaScript handlers (e.g. onclick="...") were once common, then frowned upon for mixing concerns with HTML, but remain useful when injecting HTML into the DOM because no rebinding of event handlers is necessary.

In JavaScript frameworks, creating a <Button /> component is standard practice. This encapsulates variations into a consistent API, ensuring elements get the proper classes and state-related classes are handled programmatically.

Accessibility Notes

The single most important accessibility rule is to use actual <button> elements. Replicating a button with a <div> or <span> requires manual focus and keyboard event handling, and still risks missing other expected behaviors.

Focus styles are another critical consideration. While some argue the default blue outline should remain untouched, changing it is acceptable. The worst practice is removing it entirely, for example with button:focus { outline: 0; }, without replacing it. If you remove a focus style, you must put another one back at the same time. The :focus-visible pseudo-class can differentiate mouse-based focus from keyboard-based focus when you want to suppress the outline only for mouse clicks.

Several ARIA attributes extend button functionality:

  • aria-pressed converts a button into a toggle switch, but role="switch" with aria-checked is also an option for that pattern.
  • aria-expanded indicates that the button controls the visibility of a related element, such as a dropdown menu.
  • aria-label overrides the visible text for screen readers, but a visually-hidden class can be preferable for translation purposes.
  • aria-labelledby references another element by ID to act as the label.

If a button opens a dialog, focus must move inside that dialog and remain trapped there. When the dialog closes, focus must return to the button that opened it. For actions without a context change, such as running a calculation on a button click, focus should remain on the button. However, if the action moves the user to a new “page” within a single-page app, focus should move to the start of that new page.

Buttons and links need to be adequately sized with spacing between them. Apple’s minimum touch target is 44x44pt, and other companies publish similar guidance. Fitts’s Law demonstrates that smaller targets lead to more errors, a fact Google even factors into its SEO evaluation.

Native buttons respond to clicks/touches, the Enter key, and the Space key. Applying role="button" to other elements will never provide the spacebar functionality, reinforcing that standard elements should not be replaced when a button’s behavior is needed.