Vue.js/OOUI migration guide

This page documents guidelines for migrating OOUI functionality to Wikimedia Codex components based on Vue.js. As the note above is already mentioning, this is currently only a draft and a collection of preliminary notes.


OOUI is considered Wikimedia's gold standard for accessible, internationalized, and well-styled components. Without it, we would not be here today and its lessons are needed to step into tomorrow. Like any code it is imperfect but it encapsulates literal years of learnings and should be used as a frequent reference when building new components in Vue.js.

If you have invested heavily in OOUI, we hope you will take the time needed now to document how others can learn and translate that knowledge encoded in OOUI to Vue.js with and for you. This great migration requires great guidance and the efforts of many.

Migration guidelinesEdit

"Any fool can write code that a computer can understand. Good programmers write code that humans can understand."


  • Focus on needed use cases first. For example, a button may not need to implement frameless or destructive styles initially but should assume hover, focus, and active states will be needed.
  • Split code requiring lengthier discussions into new patches and tickets. For example, if the template of a component is agreed upon but the styles are not, consider merging the template with minimal styles. There's a lot to migrate so completing even a partial component is progress.
  • Combine "frameless" into "quiet". Frameless is a legacy term for quiet. When migrating, collapse the two into quiet.
  • Avoid dependencies (explicit or implicit) and globals.
  • Some complexity in OOUI is due to browser compatibility. Some of these browsers have fallen off of the support matrix and should not hinder the new implementation.
  • Avoid inheritance.
  • Avoid unnecessary nesting. For example, the outer span in the following may be eliminated:
    <span class="oo-ui-widget oo-ui-widget-enabled oo-ui-buttonElement oo-ui-buttonElement-framed oo-ui-labelElement oo-ui-buttonWidget" aria-disabled="false" aria-labelledby="ooui-9">
      <a class="oo-ui-buttonElement-button" role="button" tabindex="0" aria-disabled="false" rel="nofollow">
        <span class="oo-ui-iconElement-icon oo-ui-iconElement-noIcon"></span>
        <span class="oo-ui-labelElement-label">Normal</span>
        <span class="oo-ui-indicatorElement-indicator oo-ui-indicatorElement-noIndicator"></span>
  • Use semantic HTML5. For example, favor button and anchor elements appropriately. For a component named button, the correct semantic HTML element to use is button for the current browser support matrix. It is possible to use an anchor but then the implementation must re-implement browser button functionality that is provided by default for proper button elements.
  • Always migrate accessibility features (ARIA roles and attributes following WAI best-practices), keyboard (accesskey and tabindex), and internationalization (for example, directionality) styles and functionality.
  • Port existing or add missing documentation. What you write should be maximally readable.


  • Use BEM CSS / Less conventions. Long term conventions are being discussed but BEM should be used in the interim.
  • Use wikimedia-ui-base for Less variables whenever possible.
  • CSS classes should separate common properties into the default class (for example, wvui-button) and state properties into state classes (for example, wvui-button--framed). This means that specifying only wvui-button is an invalid presentational state but that will be enforced by a default component state enumeration property in Vue.js.

For example, the CSS class wvui-button may be an unsupported presentation by itself, as a CSS implementation detail, but wvui-button wvui-button--progressive or wvui-button wvui-button--quiet are valid CSS class name configurations. Class names can be configured by the component's properties and computed state:

    name: 'wvui-button',
    props: {
        progress: {
            type: String,
            default: 'default',
            validator( val ) {
                return [ 'default', 'progressive', 'destructive' ].includes( val );
        framed: Boolean
    computed: {
        classes() {
            return {
                'wvui-button': true,
                'wvui-button--progressive': this.progress === 'progressive',
                'wvui-button--destructive': this.progress === 'destructive',
                'wvui-button--framed': this.framed,
                'wvui-button--quiet': !this.framed
  • HTML attributes should be favored to class names. For example, disabled instead of oo-ui-widget-disabled on an HTML `button`.

OOUI component statesEdit

The following states are generally supported and should be migrated:

  • Default or normal
  • Hover
  • Active
  • Focus

Additional states to migrate as needed:

  • Visited
  • Disabled
  • Checked or toggled

OOUI component progress action typesEdit

The following types are generally supported. Always migrate the default but only migrate progressive / destructive as needed:

  • Default or normal: generic action consequence
  • Progressive: an action that moves forward
  • Destructive: an action which destroys or has potentially negative, dangerous, or irreversible effects

OOUI component boundary and background stylesEdit

The following box boundary styles are generally supported. Always migrate the default but only migrate the rest as wanted:

  • Default or normal
  • Quiet (the legacy name is frameless): no border and no background