Last updated Apr 2, 2024

Confluence UI guidelines

These are Atlassian's internal guidelines, published for the reference of plugin developers. More thorough documentation can be found in the Plugin development guide. Not all of these guidelines are followed throughout Confluence yet, but they set the direction of our future work in the product's front-end.

Separation of content, presentation and behaviour

It is imperative that functionality in Confluence separate content, presentation and behaviour for maintainable front end code. This means the following:

  • HTML content goes in Velocity files. No CSS or JavaScript goes in Velocity files. Not even in style or onclick attributes.
  • CSS styles go in CSS files.
  • JavaScript code goes in JS files. JS files must be static and not generated by Velocity or any other mechanism.

The remainder of this document describes how to achieve this in Confluence.

Naming Conventions

At the moment we have two simple naming rules:

  • use dashes for HTML element ids or class names e.g. comment-actions
  • use camel cases for variables, method names in javascript e.g. commentToggle()

Markup

Please note as of Confluence 3.5, the DOCTYPE will change to HTML5.

Markup must be valid HTML 4 Strict and follow the Atlassian HTML coding conventions.

Use meaningful tags in your markup and do not use markup for non-semantic formatting (eg. only use <strong> for text which should have strong emphasis) or layout (that means no <table>s for layout!).

For example, the following markup is suitable for the File menu in an application:

1
2
<h3 class="menu-title">File</h3>
<ul class="navigation menu">
    <li id="menu-item-new-window"><a href="#">New Window</a> <span class="shortcut">⌘N</span></li>
    <li id="menu-item-new-tab"><a href="#">New Tab</a> <span class="shortcut">⌘T</span></li>
    ...
</ul>

The use of meaningful tags, rather than a sea of tables and divs make it rendering the page without stylesheets possible, and provide better degradation in mobile browsers, Internet Explorer, and so on.

Assign classes and IDs that allow you to style the content appropriately. Try to place classes and IDs "higher up" the DOM to enable efficient styling. In most cases, there will be a container element that can be used for this purpose.

Bad:

1
2
<div class="funky">
    <p class="my-funky-style">Foo.</p>
    <p class="my-funky-style">Bar.</p>
    <p class="my-funky-style">Sin.</p>
    <p class="my-funky-style">Qua.</p>
</div>
1
2
.my-funky-style { ... }

Good:

1
2
<div class="funky">
    <p>Foo.</p>
    <p>Bar.</p>
    <p>Sin.</p>
    <p>Qua.</p>
</div>
1
2
.funky p { ... }

Use multiple classes in markup when required, but be aware that IE6 has limitations with parsing style selectors including multiple classes. Ensure IDs are unique within the page or Javascript code will not be able to access the elements properly.

Do not use inline script and style tags. Put them in separate CSS and JS files and use #requireResource - see DOC:Including web resources below.

Put attribute values in double-quotes, use lower-case tags and attribute names in all cases. Do not use self-closing tags (e.g. <link />) or include tags or attributes that are not part of the spec. Use closing tags for all elements that support them. We want our HTML to be valid and well laid out (don't guess, use the validator!).

Although HTML 4 allows the omission of attribute values for boolean-style attributes, do not omit attribute values in Confluence HTML. Rather, set the attribute value to the name of the attribute. For example: <input type="checkbox" name="subscribe" checked="checked">. This allows better forwards-compatibility with XHTML/HTML5.

Be sure that you are familiar with how Confluence automatically HTML encodes template references.

Including Web Resources

There are two ways to have your web resources included in a page.

Call #requireResource() in your velocity template

Within a velocity template, you can use #requireResource(pluginKey:webResourceKey) to tell the WebResourceManager that you require a particular resource on this page. Note, this macro does not generate the actual markup but is done in conf-webapp/src/main/webapp/decorators/includes/header.vm via $webResourceManager.getResources().

Example of velocity code to require some web resources

1
2
#requireResource("confluence.web.resources:ajs")
#requireResource("confluence.web.resources:page-editor")

If your resource depends on a resource already provided by Confluence (or another plugin), you can add this dependency in your web resource declaration in the plugin xml. Please see Web Resource Module for the full documentaiton.

Web Resource Context

Another way to get your web resources included on a page is via contexts in your web resource declaration in the plugin xml. You can also define your own context and get all the resources for your custom context included in the page. Please refer to Web Resource Module for the full documentation.

For more information about the declaration and inclusion of web resources, see: Including Javascript and CSS resources.

Stylesheets

No more site-css.vm

We no longer have the huge site-css.vm velocity template. This has been split up into separate css files:

  • master.css, master-ie.css, wiki-content.css and more (see master-styles web resource module)
  • default-theme.css
  • colors-css.vm

The only dynamic styles in Confluence are the colors set by colour schemes, hence all color styling was extracted into colors-css.vm.

We also have a separate stylesheet for the setup wizard, setup.css.

Stylesheet ordering

CSS resources are included in the following order:

  1. Confluence default styles (resources included via calls to #requireResource such as master.css)
  2. Colour scheme styles
  3. Theme styles

Colour scheme and theme styles are also included in the header via the combined.css action call. It essentially produces a set of imports to other css resources, hence the name 'combined'.

Sample output of combined.css

1
2
@import "/s/1317/7/1/_/styles/colors.css?spaceKey=DOC";
@import "/s/1317/7/1.0/_/download/resources/com.atlassian.confluence.themes.default:styles/default-theme.css";

Note, the old monster main-action.css (i.e.StylesheetAction) has now been deprecated and split into separate actions.

Style guidelines

Use the shortest form wherever possible. That means using three character colours, combined margin declarations, simple selectors, and so on:

1
2
.menu li.menu-item a {
    color: #fff;
    background: #8ad6e8;
    margin: 0;
    line-height: 1.2;
    padding: 5px 1em;
}

Avoid child selectors like ul > li. Instead use a class name of "first" for compatibility with IE. (You can use child selectors if you want to intentionally exclude IE, however.)

When designing a new section of Confluence, consider using a style reset to clear out default styles for lists, paragraps and so on.

Internet Explorer stylesheets

Very often, it's desirable to serve custom styles to Internet Explorer. Confluence web resources can be marked as 'ieOnly' in order to be rendered in conditional comments only parsed by IE.

See Including Javascript and CSS resources for more details and an example of how to do this.

Stylesheet web resources can be include a 'media' parameter with a value of 'print' to have media='print' included on their <link> tag so they are only used for printing. See the master-styles web resource in Confluence's web-resources.xml for an example. Any media type will be passed directly into the link tag, so you can also provide styles for handheld, projector media types, etc. as supported by your user's browsers.

See Including Javascript and CSS resources for more details and an example of how to do this.

JavaScript

JavaScript guidelines

Use closures to prevent unnecessary variables and functions being exposed in the global scope. For example, the code below binds an onclick handler to a button in the page without exposing itself or its variables in the global scope:

1
2
jQuery(function ($) { // I am a closure, hear me roar!
var i = 0;
function increment() { alert(i++); }
$("button.counter").click(increment);
});

Don't introduce new global variables in JS. Rather put them under some namespace such as AJS.Editor.MyGlobalVariable.

Don't mix Velocity and Javascript code. See DOC:Passing dynamic values to Javascript if you need to pass dynamic values to Javascript.

Atlassian.js (AJS)

To avoid depending on a particular JavaScript library too much, we have atlassian.js ("AJS") as an abstraction on top of each particular library's functions. In Confluence 2.9, AJS wraps jQuery so many functions work in the same style as that library. Throughout Confluence's JavaScript, we should use AJS to make common function calls such as $, toggleClassName etc. wherever possible. This enables us to easily change the underlying JavaScript library later on if necessary.

Event Handlers

In the past, we have used embedded event handling like (horribly) so:

Sample code from common-choosetheme.vm

1
2
<tr bgcolor="ffffff" onMouseOver="style.backgroundColor='f0f0f0'"
    onMouseOut="style.backgroundColor='ffffff'"
    onclick="javascript:checkRadioButton('themeKey.default');">

We are now moving to binding event handlers in javascript, using the jquery's bind function.

Sample code from page-editor.js

1
2
AJS.toInit(function () {
AJS.$("#markupTextarea").bind("click", function () {
storeCaret(this);
});

AJS.$("#markupTextarea").bind("select", function () {
storeCaret(this);
storeTextareaBits();
});

AJS.$("#markupTextarea").bind("keyup", function () {
storeCaret(this);
contentChangeHandler();
});

AJS.$("#markupTextarea").bind("change", function () {
contentChangeHandler();
});

AJS.$("submit-buttons").bind("click", function (e) {
AJS.Editor.contentFormSubmit(e);
});
});

You may have noticed in the above example, all the binds are wrapped in a AJS.toInit() function. This is only necessary if you require the code to be fired after DOMReady.

Unfortunately, we haven't been able to port all the embedded event handler code to javascript. If you encounter such code during development, please fix it up as you go (smile)

Using jQuery directly

For advanced dynamic functionality, you can use jQuery directly. However, we don't allow jQuery to set the global $ variable (which is still used by Prototype.js), so you should use the 'jQuery' global variable as shown below.

To use jQuery properly in a JS file, do the following:

1
2
jQuery(function ($) {
// your code goes here
// use '$' for jQuery calls
});

i18n (Only 4.0+)

To get translated strings in your javascript, use the following syntax:

1
2
var label = AJS.I18n.getText("some.key");
var labelWithArgs = AJS.I18n.getText("some.other.key", "arg1", "arg2");

Then add the following transformation xml to you web resource definition in your atlassian-plugin.xml.

1
2
<web-resource key="whats-new-resources" name="What's New Web Resources">
      <transformation extension="js">
          <transformer key="jsI18n"/>
      </transformation>
....
</web-resource>

Passing dynamic values to Javascript (before 4.0)

We are now trying remove inline scripts that are scattered throughout Confluence. Most of these inline scripts are in the velocity templates so dynamic values such as i18n strings and values from actions can be used in the script. We now have a way around this via AJS.params. You simply need to define a fieldset in your template with classes "hidden" and "parameters". AJS will automatically populate itself with the inputs defined in the fieldset.

Example from page-location-form.vm

1
2
<fieldset class="hidden parameters">
    <input type="hidden" id="editLabel" value="$action.getText('edit.name')">
    <input type="hidden" id="doneLabel" value="$action.getText('done.name')">
    <input type="hidden" id="showLocation" value="$action.locationShowing">
    <input type="hidden" id="hasChildren" value="$!helper.action.page.hasChildren()">
    <input type="hidden" id="availableSpacesSize" value="$action.availableSpaces.size()">
    <input type="hidden" id="spaceKey" value="$action.space.key">
    <input type="hidden" id="pageId" value="$pageId">
    <input type="hidden" id="actionMode" value="$mode">
    <input type="hidden" id="parentPageId" value="$!parentPage.id">
</fieldset>

With the above code, you would use the above i18n edit label by calling AJS.params.editLabel in your javascript.

If your i18n message includes variables in the form {0}, {1}, etc. which are meant to be populated by JavaScript values, you can use the AJS.format() function to present them. For example:

1
2
$(".draftStatus").html(
    AJS.format(AJS.params.draftSavedMessage, time) // "Draft saved at {0}"
);

The second and subsequent arguments to AJS.format replace all instances of {0}, {1}, etc. in the first argument, which must be a String.

Passing dynamic values to Javascript (Only 4.0+)

We have revised the way we do this in Confluence 4.0 with the use of meta tags. A velocity macro #putMetadata is available for your convenience to output the meta tags in the right format. Note that any duplicate calls to put metadata for the same key will be overridden.

1
2
#putMetadata('page-id', $page.id)

This produces the following markup in the head element of the page.

1
2
<meta name="ajs-page-id" content="590712">

To access this data in javascript you can use the AJS.Meta.get api:

1
2
var pageId = AJS.Meta.get("page-id");

To view all the currently available meta tags on a page, see AJS.Meta.getAllAsMap().

Templates (Only 4.0+)

In Confluence 4.0, you now have a convenient way to use a template in JavaScript using soy. More details are documented on this page.

Rate this page: