Technical guidelines
Our technical guidelines describes the goals and rules that contributors and we (the development team of onyx) must adhere to.
Global Architecture
- 📜 onyx does not have direct dependencies, only a limited amount of peer- and dev-dependencies.
- peer-dependencies: we allow a wide range of versions (e.g.
>= 3
). - We use third-party dependencies sparingly. The need for each dependency must be well justified and documented in this wiki.
- peer-dependencies: we allow a wide range of versions (e.g.
- 📜 We rely on browser / native features as much as possible.
- 📜 Frequently used code is extracted into utils / composables.
- 📜 We provide custom eslint rules for principles and best practices where needed.
Pull Requests
- 📜 Every PR that affects the public API of any published npm libraries must include a changeset.
- 📜 A PR must consist of one (preferable) or multiple self-contained changes.
- 📜 An "Implement" ticket can and will usually be split up over multiple PRs, so a single PR doesn't have to include all feature facets.
- ❗️ These PRs should be sliced reasonably, as a set of changes that belong together.
- This enables us to do faster Code Reviews.
Documentation
- 📜 Everything we publish (features, packages, ...) must be documented
- 📜 The documentation explains everything the users need to know and do not expect implicit knowledge. E.g. all variants that a component offers are showcased and explained when they are used
- 📜 We have a well written changelog which includes all relevant information on the usage of the changes
Usability
- 📜 The DX (Developer Experience) of our users is our top priority
- 📜 We offer excellent typing support
- 📜 We only support modern technologies (ES202x)/ESM
- 📜 onyx components must work in the three big browser (engines): Chromium, Firefox and Safari
- Support is limited to versions up to one year old
- 📜 We only use Web APIs that match our browserlist
Component Interface
- 📜 Component variants that differ in regards to their aria pattern are separate components
- 📜 When Prop/Attr names of the (wrapped) native element are being used for component properties,
- they must mirror or extend the expected behavior of the native attribute
- they may limit the natives attribute accepted values (e.g.
type
property of the input could be limited to only support typetext
andpassword
)
- 📜 Fallthrough attributes should be passed to the relevant (interactive) native element of the component. (See Root Attribute Forwarding documentation)
- we use the
useRootAttrs
composable
- we use the
- 📜 All boolean properties default to
false
(falsy) - 📜 We prevent complex conditions due to big union modelValue types.
- We either offer specialized components (e.g. separating a number input from a text input)
- Alternatively, we can make use of generic components
- 📜 We provide translations for standard texts inside components, e.g. "OK", "Cancel" etc. and allow to override them.
- Community contributions to our translations are most welcome!
- 📜 We support common navigation patterns, e.g. keyboard shortcuts
- 📜 We strife to keep the parity between Figma and the implementation as high as possible.
- We use a shared domain language between UX and DEV.
- 📜 All components can be used standalone in a project without affecting styles of the whole application (so they can be used together with other component libraries). Therefore, we provide two CSS files:
- Component styles: Required. Defines the styles of the components, no side effects on globals.
- Global styles: Optional. Applies global styles to the whole application (e.g. color / background color on
body
,<a>
etc.)
Naming conventions
- 📜 All (public) components are prefixed with
Onyx
, e.g.OnyxButton
- 📜 We use v-model for two-way-data-binding wherever possible
- 📜 Component variants are named after what they are, not where they are used (e.g. a link variant "fullWidth" instead of "sideBar")
- 📜 All CSS class names are prefixed with
onyx-
- 📜 All CSS variables are prefixed with
--onyx-
Component Implementation
- 📜 We don't clone DOM nodes. Instead, we can place slots in different places depending on the breakpoint
- 📜 We only use as much JavaScript as necessary (e.g. prefer CSS solutions over JavaScript)
- 📜 Property types are declared and exported as standalone type and referenced in the
defineProps
- the property type can still be declared (and exported) inside the component file to which it belongs
- 📜 We always use the notation
props.foo
instead of accessingfoo
directly in the<template>
section - 📜 We structure the component code in the following manner:
- types
- props
- emits
- slots
- ... everything else grouped by responsibility
- defineExpose
A11y (Accessibility)
- 📜 We use semantic HTML
- 📜 We keep the DOM tree as flat as possible
- 📜 We follow A11y best practices (level AA)
- 📜 We define aria relevant properties as required
- e.g. the
alt
property of theOnyxImage
component is required
- e.g. the
Typescript
- 📜 We use
type
s instead ofinterface
s - 📜 We don't use typescript enums
- 📜 We use strict undefined/null checks when implementing the onyx ecosystem
###########
CSS
- 📜 Instead of using
scoped
/module
for<style>
, we rely on meaningful class structures- we use BEM to provide semantic and structured class names
- all styles related to a component are bundled inside one component class, e.g. the component
OnyxInput
has one classonyx-input
which groups all styles of that component
- 📜 We don't use
!important
- 📜 We define only as little style as needed
- Instead of copying Figma styles, we evaluate what is needed
- 📜 Component styles are limited to the component internals
- We don't set opinionated margins on the components that define how they are spaced in the page layout.
- 📜 We avoid writing styles on a parent component that are applied to children that are placed in a slot
- Exception: When the alternative would over-complicate the interface, e.g. by requiring extra properties
- 📜 We prefer styles that don't rely on
:not()
- 📜 We use
rem
for everything. This includes borders, font-sizes etc.
Testing
📜 Each (public) component is covered by at least 1 screenshot test (via Playwright)
📜 All defined component behavior is covered by a playwright test (
.ct.tsx
file)📜 Utils and composables are covered by Vitest tests (
.spec.ts
file)📜 We structure our tests by using the arrange-act-assert pattern
ts// ARRANGE // ACT // ASSERT