Our technical guidelines describes the goals and rules that contributors and we (the development team of onyx) must adhere to.
Copy link to headline:Global Architecture
- ๐ onyxdoes 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.
Copy link to headline:Pull Requests
- ๐ Every PR that affects the public API of any published npm libraries must include a changeset(opens in a new tab).
- ๐ 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.
Copy link to headline: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
Copy link to headline:Usability
- ๐ The DX (Developer Experience) of our users is our top priority
- ๐ We offer excellent typing support
- ๐ We only support modern technologies (ES202x)(opens in a new tab)/ESM
- ๐ onyxcomponents 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
Copy link to headline:Component Interface
- ๐ Component variants that differ in regards to their aria pattern(opens in a new tab) 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.
typeproperty of the input could be limited to only support typetextandpassword)
- ๐ Fallthrough attributes(opens in a new tab) should be passed to the relevant (interactive) native element of the component. (See Root Attribute Forwarding documentation)
- we use the
useRootAttrscomposable
- 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(opens in a new tab)
- ๐ 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.)
Copy link to headline:Naming conventions
- ๐ All (public) components are prefixed with
Onyx, e.g.OnyxButton - ๐ We use v-model(opens in a new tab) 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-
Copy link to headline: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.fooinstead of accessingfoodirectly in the<template>section - ๐ We structure the component code in the following manner:
- types
- props
- emits
- slots
- ... everything else grouped by responsibility
- defineExpose
Copy link to headline:A11y (Accessibility)
- ๐ We use semantic HTML(opens in a new tab)
- ๐ We keep the DOM tree as flat as possible
- ๐ We follow A11y best practices(opens in a new tab) (level AA)
- ๐ We define aria relevant properties as required
- e.g. the
altproperty of theOnyxImagecomponent is required
- e.g. the
Copy link to headline:Typescript
- ๐ We use
types instead ofinterfaces - ๐ We don't use typescript enums(opens in a new tab)
- ๐ We use strict undefined/null checks when implementing the onyx ecosystem
Copy link to headline:CSS
- ๐ Instead of using
scoped/modulefor<style>, we rely on meaningful class structures- we use BEM(opens in a new tab) to provide semantic and structured class names
- all styles related to a component are bundled inside one component class,
e.g. the component
OnyxInputhas one classonyx-inputwhich 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(opens in a new tab) for everything. This includes borders, font-sizes etc.
Copy link to headline: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.tsxfile) - ๐ Utils and composables are covered by Vitest tests (
.spec.tsfile) - ๐ We structure our tests by using the arrange-act-assert pattern(opens in a new tab):br