Component Interfaces and Implementations
I’ve been thinking recently about how to define components in a design system; what makes a component good. Not components that look good, but ones that are well defined and future-proof. A good component is one that is useful, understandable, and easy to update without breaking consumers.
We can break down a component into two pieces: the API, and the implementation. The API is how consumers use the component, and the implementation is how the component owner defines the appearance and functionality.
This is where taking a look at more traditional programming languages like Java or Objective-C can help define what makes a good component. The API of a component is similar to a class interface, and the implementation is well, the implementation of the class.
If you are familiar with Javascript and React, a class interface is similar to a type definition in TypeScript or a React component’s PropTypes. A class interface is an API contract to the user. The underlying implementation can change as long as the interface remains the same.
An interface describes the methods, or method signatures, and attributes a class should implement. You could also think of it like an abstract class in a language like Ruby, where you have to override all the methods and attributes. Either way it is the same; a construct to describe the API of a class.
An interface in Java looks like this:
public interface Person {private static String name;public void talk();}
Then you can have an implementation of Person
:
public class Astronaut implements Person {private static String name = "Major Tom";public void talk() {System.out.print("Hello World");}}
Now we can change the underlying implementation, Astronaut
, as long as it still properly implements the interface, Person
:
public class Astronaut implements Person {private static String name = "Ground Control";public void talk() {System.out.print("I'm stepping through the door");}}
If you aren’t used to a language with interfaces and implementations, for a class to be a valid implementation it must have the same methods, or method signatures, and attributes. How those methods function and what the value of the attributes are, are up to the implementation to define.
A well thought out class interface rarely changes, just like a well thought out API should rarely change as well. If you keep adding or changing API endpoints or interface methods, it becomes burdensome for consumers and signals that it might have been poorly defined from the beginning. Similarly, a well thought out component’s interface or API should rarely change, even if the implementation changes.
This gets to what Ysenia Perez-Cruz describes as scenario driven components. The definition of the component is based on a scenario rather than a presentation. This could also be described as semantic versus presentational components. A semantic component clearly separates the interface from the implementation whereas a presentational one tightly couples them.
Scenarios, not layout should drive variation.
— Ysenia Perez-Cruz
I’ve been working on some data visualization components in a design system, let’s use this as an example (don’t judge the design of this, it is just an example):
Depending on how we structured and named the API for this component, it could either be semantic or presentational. A presentational version might look like this:
public interface DataVisualization {private static String rightHeader;private static String leftHeader;private static String footerButton;private static String footerText;private static DataSet dataSet;}
This component interface is presentational because the API is based on how it looks rather than what it does. This makes the component very brittle and rigid. I can’t change where the footerText
is displayed without breaking what it means to people using the component. I could instead write the interface like this:
public interface DataVisualization {private static String title;private static String subTitle;private static String action;private static String metaData;private static DataSet dataSet;}
This now describes what each part does, rather than how it might be implemented visually. This allows the component owner to make visual updates, changing the implementation, without breaking the API. Now we are free to change where the metaData
is presented, maybe we move it above the graph. If it were called footerText
this attribute name would no longer make sense. For example we could update the component to look like this without breaking the meaning of it:
This also has the added bonus of being more localizable. For right-to-left languages attributes like leftHeader
and rightHeader
don’t make sense. If you really wanted to make the component API based on layout, a more localized version would be startHeader
and endHeader
, or something similar.
Component naming
Similar to how the component API should be based on its scenario or use case, the name of the component itself should reflect the use case of the component rather than how it looks. A presentationally named component cannot change how it looks without breaking what the user expects the component to do. One example is:
- Horizontal Scrolling Tiles: This component name is presentational; it describes how the component looks. You know when you use this component, it will be tiles arranged horizontally that scrolls.
- Key Data Points: This is the same component, but named based on the use case. It is meant to display key data points. They could be displayed in horizontal tiles, or maybe they are displayed in a bar or some other cool way I don’t even know.
Going too far in semantic naming
Taken to an extreme, this can cause issues if you define components so strictly they do not make sense for other areas of your application to use. For example we could define the chart above as a TrafficChart
, but that would severely constrain the scenarios it can be used for.
Good presentational components
Presentational components are not necessarily a bad thing, just use them purposefully. For example layout type components like cards or grids are fine. Generally the more atomic the component, the more presentational it becomes and that is ok.
General tips for naming
- Words like ‘primary’, ‘secondary’, ‘tertiary’ are good because they describe the importance, which could be implemented in a number of ways: text size, color, weight, position.
- Typographic definitions like ‘title’ and ‘subtitle’, and ‘prefix’ and ‘suffix’ make the API generic, but meaningful.
- T-shirt sizing allows for a middle ground of presentational and semantic. The component can decide what ‘large’ means, but still give consumers the ability to choose a size.
- Try to keep attribute names consistent across components. If you use ‘variation’ in some components, and ‘type’ in others to mean the same thing it can get confusing.
Thinking about the scenario and the semantics of the component before doing any design or development work is important to get right from the start. The component name and API is a contract to the user, that this component will do what it says.