This question comes up often and the answer varies greatly based on individual designers' workflow preferences. That said, we generally recommend turning things into components fairly early in the design process. Once you have elements repeated across multiple screens, it's a good time to start thinking about components (even if you at fairly low-fidelity stages of your project). The design may change and go through many refinements, but creating components at this stage means you can save time later by making those changes once (with the original component), and having them update across all of your screens (with the instances).
Main components live wherever you create them, usually in context within your design, which makes them easy to tweak as your design progresses (except for bigger companies which house their main components in a separate design system file). If you feel like you're getting hung up on components too early at an exploratory stage of your project, worry about them later—don't let it hinder the fluidity of your design process.
As your team decides to adopt these patterns to reuse them in other products as part of your design system, then you can consider consolidating them from individual project files into a more formal document dedicated to serving as shared library. During that process you may choose to refine their structure/naming/properties so that they are easier to use and consistent with other components. How might you design better components? Let's unpack some considerations and best practices!
When you begin creating high-quality components, especially with the intention of sharing them, consider starting with smaller atomic components that can be used as building blocks. You'll notice patterns in your design that you want to be consistent. For example, the shape of a card, or the shape of a button.
A good strategy is to turn these repeated elements into a component that you can reuse by nesting instances of them inside other components. Imagine turning a simple button shape into a component (to be used as an atom), and nesting it within every button component that you create. The result: all buttons use the exact same starting point; if that shape were to change, it can be updated by changing that single atomic component.
Continuing with the button example, let's assume you also have primary and secondary variants, desktop and mobile versions, each with 4 states (normal, disabled, pressed, and focused). You would potentially have 16 different button components that you would otherwise have to edit if you didn't build this in an atomic fashion. Structuring components in this way will make your system much more maintainable.
Quick tip: By prefixing component names with a '_' or a '.', they will be excluded from publishing. Consider which components designers will need to use; they may not need to access all of the smaller atomic components. By excluding these from the publishing process, you can improve the experience for the users of the libraries since you will eliminate these from showing up in the components panel.
As you're thinking of UI components, you're most likely also considering how to handle related components—these could be additional states, themes like light or dark, or other variations. What's the best way to handle them? How will designers interact with them?
When you place an instance of a component into your design, Figma gives you access to its layers in the layers panel. This enables you to view and expand each instance in the layers panel. You may consider nesting elements in your component which users can turn on (by toggling the layer visibility) when needed; you could even nest every state within a single component. This method does have some benefits, but also a few drawbacks.
Benefits: There is a single Figma component to share with users. This means one component to maintain, one component for designers to use, and only one to find in the components panel and instance menus.
Drawbacks: This method can make it more difficult for designers for discover the different states within the layer stack of each component. Switching between them can be more cumbersome since designers must know to toggle the appropriate layers within the component. These states are not always immediately apparent unless designers expand the component in the layers panel. This method can also result in a lot of repeated layers within your component. For example, a button comprised of a text box and a rectangle may need to repeat those layers for every state—when users override the text, and later want to show a different variation/state, the designer will need to re-input the override. This use case is handled much smoother with the following approach.
Another approach, and one that we typically recommend, is to create separate components for each variation or state.
Benefits: With this method, all variations are easily accessible from the components panel and the instance menus; making it easier to find and switch between them. Instead of toggling the right combination of nested layers, designers can simply choose a different related component from the instance menu to switch to. This method can preserve your text overrides when switching instances if the text layers are named the same in both components. This method also tends to be more performant; we've seen some examples of very complex components with hundreds of hidden layers (using the nested approach), that could be greatly simplified using this approach.
Drawbacks: With this method you will ultimately end up with more components to share and maintain. However, if you couple this method with the atomic structure described above, you can build your components in a way that makes them easier to maintain. Lastly, since many visual variations may be very subtle and difficult to discern in a thumbnail preview, consider adding useful descriptions to your components (they will surface as tool tips on hover in the components panel).
There may also be times when you wish you create variants of components with different visual properties—for example: for themes, for light and dark modes, or for different brands. One technique you can use to achieve this is to select an existing component instance, override the visual properties you want to change, and then create a component from it. This will nest an instance of the original component inside a new component. This will preserve the newly applied overrides, but will still maintain a connection with the original component—this makes the design easier to maintain since you only have to adjust the design in one place. The image below highlights a couple potential use cases.