As the Bikeshed Documentation says: "Bikeshed is a spec-generating tool that takes in lightly-decorated Markdown and spits out a full spec, with cross-spec autolinking, automatic generation of indexes/ToC/etc, and many other features."
This document is intended as a simple introduction to writing specifications of web APIs using Bikeshed. Very extensive documentation of Bikeshed is available, but it is a complex tool and there is a lot to learn before one can become productive.
So the goal here is to help beginners get started using Bikeshed in the simplest way possible. And then we provide some guidance for starting to write the first few revisions of a specification. We only cover the basics for many topics and refer to other documents for more complete details. The following topics are covered here:
You can create a repository for your specification in a variety of ways. This guide will start with having you create a directory in your local work environment and set up the required tools. In a shell window, do the following, replacing "widgets" with the name of your spec:
mkdir widgets
cd widgets
If it is not already available on your development platform, Download and install Node.js. Node will also be used to help setup your development environment and can be used to run visual tests. This will also install the npx
command which will be used next to create the initial repository files.
Maybe run npm init
which will prompt you to provide some information about your project, such as the project name, version, description, entry point, etc. Then it will create a package.json
file.
npm install pnpm
vnu-jar
: pnpm add vnu-jar
You should also install git, if it is not already available.
To easily create and initialize a new repository with content, first create a new directory (e.g. my-awesome-api
) and cd
into it. Then run the following:
npx wicg init "My Awesome API"
This command will ask you for meta-level details about your specification, which you can change later. You should select 'bikeshed' as the spec preprocesor so that it will generate an index.bs
file, and it will also create the following files as the initial content for your :
CONTRIBUTING.md
index.bs
-LICENSE.md
.pr-preview.json
README.md
- an explainer for your specThe above npx
command created an initial index.bs
file. Alternatively, you can copy this Minimal template, or you can run bikeshed template > index.bs
to generate the same template. Here is what the bikeshed generated template looks like:
<pre class='metadata'>
Title: Your Spec Title
Shortname: your-spec
Level: 1
Status: w3c/UD
Group: WGNAMEORWHATEVER
URL: https://dlaliberte.github.io/bikeshed-examples/template.html
Editor: Your Name, Your Company http://example.com/your-company, your-email@example.com, http://example.com/your-personal-website
Abstract: A short description of your spec, one or two sentences.
</pre>
Introduction {#intro}
=====================
Introduction here.
The Bikeshed Documentation: Installation provides details on several different ways for how to install and run Bikeshed. We recommend you install it on a local machine so you can run it multiple times as you make changes to the specification.
pip3 install bikeshed
bikeshed update
The index.bs
template created above will contain only a "metadata"
section and an Introduction, but when you run bikeshed spec index.bs
, the generated index.html
file will include the following sections (plus two added sections which are required in all specifications).
You can see what this looks like an an html page at Bikeshed spec template - using the default template.
About this specification:
Abstract: From the metadata.
Status of this document: From the metadata.
Table of Contents: Generated automatically from sections.
Introduction: This section provides an overview of the purpose and scope of the standard.
Security considerations: Add this section, which describes security issues related to the standard and provides guidance on how to mitigate them.
Privacy considerations: Add this section, which describes privacy issues related to the standard and provides guidance on how to address them.
Conformance: This section describes how to conform to the standard and specifies the requirements for conformance.
Document conventions: Generated content.
Conformant Algorithms: Generated content.
References:
Assuming you will be using GitHub to develop and provide public access to your specification, you should first decide whether you will use an existing repo or create a new repo to serve as the "publishing source" repo for your specification. You'll need to be an admin for this source repo.
It is also convenient to set up a "GitHub Pages site" and actions which will automatically publish and serve the html file that is generated by Bikeshed after each change of your source file. To do that, make the following changes.
Follow instructions for Publishing with a custom GitHub Actions workflow. I.e. go to "Settings" > "Pages" to show "GitHub Pages"
Under "Build and deployment" > "Source", select "Deploy from a branch", if not already selected.
Under "Settings" > "Actions",
.github/workflows/
) to build.yml
or some-other.yml
.index.bs
and index.html
if your name is different.name: Build
on:
pull_request: {}
push:
branches:
- main
permissions:
contents: write
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: w3c/spec-prod@v2
with:
TOOLCHAIN: bikeshed
SOURCE: index.bs
DESTINATION: index.html
GH_PAGES_BRANCH: gh-pages
BUILD_FAIL_ON: warning
npx
command above, the configuration file will be installed for you.Now you can repeat the following development cycle:
index.bs
, locally, run Bikeshed with bikeshed spec index.bs
and preview the resulting html in your browser. Fix any errors until satisfied.index.html
in GitHub Pages.
github.com/owner/repo-name
, then your html file will be at owner.github.io/repo-name/index.html
.Bikeshed uses a Markdown variant called Bikeshed-flavored Markdown (BSMD). By default, Bikeshed recognizes all of the "block-level" Markdown constructs defined by CommonMark, except for indented code blocks. You can freely switch back and forth between Markdown and HTML as needed, but you should prefer to use Markdown where it is available.
In web browsers, a JavaScript function call with JS data parameters cause the browser to call an internal API function, typically implemented in C++. The result of the call, if any, is then returned as a JavaScript value. Web standards specify these function interfaces in WebIDL, and to further clarify the meaning of each function, we specify the algorithm for the function in terms of WebIDL and Infra.
WebIDL and Infra are complementary technologies. WebIDL is used to define the interfaces of web APIs, while Infra is used to define the algorithms of those interfaces with procedural steps.
JS types: boolean, number, array, object, ... Web IDL types: boolean, unsigned long, sequence, dictionary, ... Infra types: boolean, bytes (for strings), lists, ordered maps |
Once you have created an initial spec document, it might be easiest to follow the following process to incrementally refine your spec.
If you have WebIDL specifications for your API code, that is a great place to start. Simply copy-paste a subset of the WebIDL that corresponds to the public API into an <xmp class="idl">
tag. We recommend using the <xmp>
tag rather than the <pre>
tag so that you will not need to HTML-escape &
and <
characters.
You will typically define your algorithm steps relative to an interface declared with WebIDL. Here is an example of an interface that is used in the following sections.
<xmp class="idl">
interface Foo {
attribute DOMString bar;
constructor(DOMString arg1);
long baz(DOMString arg1);
};
</xmp>
Immediately before or after each WebIDL block, it is important to include a short, non-normative description, or "domintro"
for each property defined. These descriptive blocks are especially important for algorithmic specifications which are otherwise difficult to read.
Here is the suggested markdown for a domintro
block, which Bikeshed will convert into an HTML definition list:
<div class="domintro">
: property
:: Brief summary of property
</div>
CSS for the domintro
class is defined below, which you should include in your spec.
Once you have some WebIDL declarations of functions and types of parameters, then you can define an algorithm for each function in terms of Web IDL along with Infra declarations for internal state. Use a <div class="algorithm">
container for your algorithm steps, so Bikeshed can add nice default styling to make the algorithms easier to read. For example:
<div algorithm="Foo-algorithm">
Attribute definitions, if any...
The algorithm steps are:
1. step-1
1. step-2
1. step-3
</div>
Note that the steps are all numbered 1.
, since markdown automatically increments the numbers for you, so you won't have to update numbers manually.
WebIDL definitions typically define one or more attributes that are associated with an instance object. By default these attributes refer to some internal state of the object, not directly observable by author-facing JS, but may be referenced by other spec algorithms.
For example, given this WebIDL:
<xmp class="idl">
interface Foo {
readonly attribute DOMString bar1;
attribute DOMString bar2;
};
</xmp>
[TODO: resolve what to say about "internal slots".]
This WebIDL implies that Foo
instances have internal slots for [[bar1]]
and [[bar2]]
.
When referencing an attribute in spec algorithms, you must refer to the internal slot, not the author-facing property, as those can be observed/intercepted by author code. You can use text like "...the {{Foo/bar1}} internal slot
...". But current best practice is that instead of referring to internal slots, you should use language like this: "Foo
has an associated bar1
of type DOMString
".
The bar1
attribute is readonly
which means it only has a getter algorithm that could be defined like this:
<div algorithm="Foo.bar1">
The <dfn attribute for=Foo>bar1</dfn> [=getter steps=] are: ...
</div>
The bar2
attribute is read/write
so it has both a getter and a setter. Use something like the following markup to define these algorithms:
<div algorithm="Foo.bar2">
The <dfn attribute for=Foo>bar2</dfn> [=getter steps=] are:
1. Return [=this=]'s {{Foo/[[internalBar2]]}} slot.
The {{Foo/bar2}} [=setter steps=] are:
1. Set [=this=]'s {{Foo/[[internalBar2]]}} slot to [=the given value=].
</div>
Note that within getter and setter algorithm steps, you implicitly have access to the instance, [=this=]
. Within setter steps for read/write
attributes, you also have access to [=the given value=]
, which is the value that the attribute is being set to.
If your attribute getter or setter needs to do something non-trivial, such as reacting to its state in ways that the WebIDL type system does not, you may need to explicitly define a name like: <dfn for=Foo>[[bar2]]</dfn>
(since Bikeshed doesn't auto-define the [[bar2]]
slot name for you yet).
All definitions (except dfn
definitions, by default) are automatically "exported", which means other specs can autolink to them.
If a definition is not exported, it is considered private, but you can still link to it with e.g. "<a spec: something>...</a>
".
Every method needs an algorithm defining it. Given an interface like:
<xmp class="idl">
interface Foo {
long baz(DOMString arg1);
};
</xmp>
Use markup like:
<div algorithm="Foo.baz()">
The <dfn method for=Foo>baz(DOMString |arg1|)</dfn> [=method steps=] are:
1. Do something to |arg1|.
1. If [=this's=] {{Foo/bar}} attribute is null, [=throw=] a TypeError.
1. Otherwise, return 4.
</div>
By using the variable markup |arg1|
for an argument in the defining signature, you can refer to the argument within the method steps using the same notation, as shown in step 1 above.
Within all method algorithm steps, you implicitly have access to [=this=]
, the instance object being operated on.
If you don't define a constructor for a class, one gets created automatically for you that just throws. If you actually want your object to be constructable, it's very similar to methods. Given IDL like:
<xmp class="idl">
interface Foo {
constructor(DOMString arg1);
};
</xmp>
Use markup like:
<div algorithm="Foo()">
The <dfn constructor for=Foo lt="Foo(arg1)">new Foo(DOMString |arg1|)</dfn> [=constructor steps=] are:
...
</div>
(Bikeshed will make all this slightly easier in the future, see https://github.com/speced/bikeshed/issues/2525.)
Defining a term, such as for a type, object, constant, concept, or a top-level entity, is usually as easy as wrapping a <dfn>
element around it. Bikeshed can then automatically link from each reference of a defined term to its definition. You can reference a definition by its name with [=name=]
, or you can specify a different display name with [=name|display name=]
. Here is an example of a simple definition and a couple different references to it.
The user agent has a <dfn>really useful object</dfn> that ...
...
1. If the [=really useful object=] is null, then …
2. If the [=really useful object|RUO=] is not null, then …
There are several convenient ways that Bikeshed will Autolink from use of a term to its definition. Here is a table of some of them (derived from a Bikeshed Cheat Sheet)
Definition Notation | Meaning |
---|---|
<dfn>foo</dfn> |
A definition for foo |
## heading {#link-name} |
Creates a new <h2> that is linkable by #link-name |
Link Notation | Meaning |
---|---|
[=foo=] |
Link to definition of foo |
{{foo}} |
A reference to the IDL entry for foo |
[[foo]] |
A non-normative reference to the SpecRef entry foo |
[[!foo]] |
A normative reference to the SpecRef entry foo |
[[#foo]] |
A reference to the section in the local document named foo |
[[foo#bar]] |
A reference to section bar of spec foo . The spec must be part of Bikeshed's autolinking database. |
'foo' |
Link to a CSS property or descriptor named foo |
Domenic's guide to spec excellence - Docs (mostly tactical "how to start a new spec")
Slides: How to read, write, and think about specs and video: Writing good specs
Writing Procedural Specs - A very helpful document, by Gary Kac, similar in purpose to this one.
Notes from a talk to WebML WG - Joshua Bell's high-level perspective on writing web specs and best practice tips for using Bikeshed.
<style>
/* domintro from https://resources.whatwg.org/standard.css */
dl.domintro {
position: relative;
color: green;
background: #DDFFDD;
margin: 2.5em 0 2em 0;
padding: 1.5em 1em 0.5em 2em;
}
dl.domintro dt, dl.domintro dt * {
color: black;
font-size: inherit;
}
dl.domintro dd {
margin: 0.5em 0 1em 2em; padding: 0;
}
dl.domintro dd p {
margin: 0.5em 0;
}
dl.domintro::before {
content: 'For web developers (non-normative)';
background: green;
color: white;
padding: 0.15em 0.25em;
font-style: normal;
position: absolute;
top: -0.8em;
left: -0.8em;
}
</style>