Best Practices and Patterns

Best practices and patterns for using DevLink

There are many different ways of using DevLink in applications. Some common goals in practice might be:

  • A “design systems” approach for building components in isolation in Webflow, and creating a library of composable components in React apps
  • Building full pages as components in Webflow, and exporting them via DevLink
  • Building skeleton layouts in Webflow, designing for dynamic content in mind

In this guide, you’ll find a collection of best practices and patterns for setting up DevLink components in Webflow and React for best maintainability and performance.

Best practices

Component size and structure

When building components in Webflow, you can choose between two main approaches to component architecture, or even mix them together based on your specific needs:

  1. Complex Components: These are larger, self-contained components that represent complete UI sections or features. They’re ideal when you want to maintain consistency between your Webflow site and React app for entire sections of your interface.

    • Use these for components that need to look and behave identically across both platforms
    • Examples:
      • Navigation bars (with all their states and interactions)
      • Complete pricing tables (with all cells and styling)
      • Full footers (with all links and sections)
      • Entire page sections (like hero sections or feature blocks)
  2. Atomic Components: These are smaller, reusable building blocks that form the foundation of your design system. They’re perfect for creating a consistent look and feel across your application.

    • Use these for basic UI elements that you’ll combine in different ways
    • Examples:
      • Buttons (with different variants)
      • Headings (with different sizes)
      • Links (with different styles)
      • Cards (with different layouts)
Don’t manually alter DevLink-generated component code, as the webflow devlink sync command will override any changes

Accessibility

DevLink components inherit their accessibility features from the Webflow elements they’re built upon. To ensure your components meet accessibility standards, follow Webflow’s accessibility guidelines. For detailed information on creating accessible elements in Webflow, refer to the Webflow Accessibility Guide.

Patterns

Repeatable lists

When working with lists of data in DevLink (like tables or item lists), we recommend splitting your components into two parts for better reusability and maintainability:

  1. A container component with a slot property that defines the overall structure and styling of the list
  2. An item component that handles the rendering of individual list items

For example, let’s say you’re building a user management interface. You would create:

  • A UserTable component in Webflow that defines the table structure and contains a slot property for the table body
  • A UserTableRow component that defines how each user’s data should be displayed in a row

This separation allows you to:

  • Keep the table structure consistent while dynamically rendering different data
  • Reuse the row component in other contexts if needed
  • Maintain a clear separation between the container and its contents

The example code below shows how to use these components together to display a list of users.

UserDashboard.tsx
1import { UserTable } from "@/devlink/UserTable";
2import { UserTableRow } from "@/devlink/UserTableRow";
3import { useQuery } from "react-query";
4
5// Replace this with your own data
6const fetchUsers = () =>
7 Promise.resolve([
8 {
9 id: 1,
10 image: "https://d1otoma47x30pg.cloudfront.net/64669c4daefff35dc8536828/64669c4daefff35dc85368b4_sophie-moore-avatar-dashflow-webflow-template-p-130x130q80.jpg",
11 name: "Sophie Moore",
12 email: "sophie@dashflow.com",
13 jobTitle: "CTO & Co-Founder",
14 active: true,
15 company: "BRIX Templates",
16 role: "Member",
17 },
18 {
19 id: 2,
20 image: "https://d1otoma47x30pg.cloudfront.net/64669c4daefff35dc8536828/64669c4daefff35dc85368b6_andy-smith-avatar-dashflow-webflow-template-p-130x130q80.jpg",
21 name: "Andy Smith",
22 email: "andy@dashflow.com",
23 jobTitle: "VP of Marketing",
24 active: false,
25 company: "BRIX Templates",
26 role: "Member",
27 },
28 ]);
29
30const Users = () => {
31 const { data: users } = useQuery({
32 queryKey: ["users"],
33 queryFn: fetchUsers,
34 });
35
36 return (
37 <UserTable
38 tableContent={ // This is the slot property in the Webflow component
39 <>
40 {(users ?? []).map(({ active, id, ...user }) => (
41 <UserTableRow
42 {...user}
43 key={id}
44 greenBadgeVisibility={active}
45 redBadgeVisibility={!active}
46 greenBadgeText="ACTIVE"
47 redBadgeText="INACTIVE"
48 />
49 ))}
50 </>
51 }
52 />
53 );
54};
55
56export default Users;

Webflow classes

When using DevLink, you may want to reuse Webflow classes in your custom React components. This is useful for maintaining the same styles and design across both Webflow and the web app.

If you are using CSS Modules in your app (default with DevLink), then you can import the relevant component CSS module and use it to access the classes in your custom components.

1import buttonModule from "@/devlink/Button.module.css";
2
3const MyPrimaryButton = () =>
4 <a className={"w-button " + buttonModule["button-primary"]}>Primary button</a>;
5
6const MySecondaryButton = () =>
7 <a className={"w-button " + buttonModule["button-secondary"]}>Secondary button</a>;

In the above example, we add the w-button class to add Webflow’s default button styles.

Without CSS modules

If your app isn’t using CSS Modules, you can import the relevant component .css file which will have all the component classes defined, and use the classes directly in your custom components.

1import "@/devlink/Button.module.css";
2import "@/devlink/global.css";
3
4const MyPrimaryButton = () =>
5 <a className={"w-button button-primary"]}>Primary button</a>;
6
7const MySecondaryButton = () =>
8 <a className={"w-button button-secondary"]}>Secondary button</a>;

If you aren’t using CSS Modules, make sure component styles don’t conflict with other component styles in your web app.

With DevLink builtin elements, you don’t need to set Webflow default classes when using CSS Modules. Using the builtin elements ensures you also reap out-of-the-box benefits from Webflow (for example, pre-load functionality)

1import { Link } from "@/devlink/_Builtin";
2import buttonModule from "@/devlink/Button.module.css";
3
4const MyPrimaryButton = () => (
5 <Link
6 className={buttonModule["button-primary"]}
7 button={true}
8 options={{
9 href: "https://webflow.com",
10 target: "_blank"
11 }}
12 >
13 Primary Button
14 </Link>
15);
16
17const MySecondaryButton = () => (
18 <Link
19 className={buttonModule["button-secondary"]}
20 button={true}
21 options={{
22 href: "https://webflow.com",
23 preload: "prefetch"
24 }}
25 >
26 Secondary Button
27 </Link>
28);

Webflow forms

Use Webflow forms in your React app in two main ways:

  • A single Webflow form exported as a whole component
  • Composable forms via Webflow element components (for example, inputs, dropdowns, submit buttons, etc.)

Single Webflow form component

For simple forms (for example, one or two inputs without complex validation), we recommend exporting an entire Webflow form as a single component with DevLink. This will enable you to use the Webflow form as an uncontrolled React form in your app.

When you create a component from a Webflow form, add a Runtime prop to the root Form element in Webflow (for example, formProps). This will enable you to pass in an onSubmit event handler prop in your app. For example, consider a simple newsletter sign up form with an email and a submit button called NewsletterSignupForm.

Newsletter Form UI
Newsletter Form Settings

After importing this component into your app, using it in a React component might look like the following:

1import { NewsletterSignupForm } from "@/devlink/NewsletterSignupForm";
2
3export function FooterSignup() {
4 return (
5 <NewsletterSignupForm
6 // Runtime prop
7 formProps={{
8 /* If an exception is raised by this function,
9 the Error state is automatically rendered.
10 Otherwise, Success state is rendered. */
11 onSubmit: (e: React.FormEvent<HTMLFormElement>) => {
12 e.preventDefault();
13 fetch("/newsletter-signup", {
14 body: new FormData(e.target),
15 method: "POST",
16 });
17 },
18 }}
19 />
20 );
21}

Composable forms with Webflow elements

If you’d rather build a form with finer control over state, validation, and submission, you can export form elements from Webflow and compose them in your app as needed. This approach is recommended for more complex and interactive forms, while allowing for consistent design and interactions across your Webflow site and React app.

For example, taking the same Newsletter above, you could export the Input and FormButton elements via DevLink into your project. Then, you can compose them in your app with a <form> element and the use of 3rd party libraries to handle form state and validation.

1import { Controller, useForm } from "react-hook-form";
2import { yupResolver } from "@hookform/resolvers/yup";
3import * as yup from "yup";
4
5import { EmailInput } from "@/devlink/EmailInput";
6import { SubmitButton } from "@/devlink/SubmitButton";
7
8// Define custom schema for validation
9const schema = yup
10 .object({
11 email: yup.string().email().required(),
12 })
13 .required();
14
15export function FooterSignup() {
16 const {
17 handleSubmit,
18 control,
19 formState: { errors },
20 } = useForm({
21 resolver: yupResolver(schema),
22 });
23
24 return (
25 // We can use the native HTML form element now that our state is controlled
26 <form
27 onSubmit={handleSubmit(async (data) => {
28 try {
29 fetch("/newsletter-signup", {
30 body: new FormData(e.target),
31 method: "POST",
32 });
33 } catch (err) {
34 alert("Error!");
35 }
36 })}
37 >
38 <Controller
39 name="email"
40 control={control}
41 render={({ field }) => (
42 <EmailInput
43 inputProps={{
44 ...field,
45 placeholder: "Your email",
46 type: "email",
47 "aria-invalid": errors.email ? "true" : "false",
48 }}
49 />
50 )}
51 />
52 <SubmitButton />
53 </form>
54 );
55}

Custom code

If you’ve added custom CSS to a Webflow page using custom code, note that it won’t be included in the DevLink-generated component since Webflow Page custom code isn’t exported with components. Also, generic class selectors (for example, [class="my-class"]) won’t work since class names are namespaced uniquely.

To bring over any custom CSS to the web app that targets DevLink components:

  1. Add an Embed element inside the component
  2. Update custom CSS selectors to use the following pattern: [class*="<ComponentName>_<class-name>__"] {...}

For example, take a Hero Section component that has an element with a class "Hero Heading Center". In the Embed element, you can add the following custom CSS to target the namespaced class to add the custom CSS without worrying about it breaking in the DevLink generation.

Custom CSS selector targeting
The selector will remain consistent as long as the component and class name don’t change

Element tags

To bind an element tag to certain Webflow elements at runtime, there are a few recommended practices:

  • For Heading elements, you can bind a prop to the Tag setting in Webflow and set the prop to "h1", "h2", etc. to set the correct heading level
Prop on Tag setting for Heading element
  • For div elements, you don’t need to set up a Webflow prop to bind on the element. Simply set the as prop to the desired element tag at runtime (for example, as="section")

    1<DynamicDiv as="section" />

Importing DevLink components directly from ./devlink may be convenient. However, this could lead to bloated bundles and performance issues if your app’s bundler includes all components from the DevLink directory (including unused ones).

Instead, import specific components directly from the component source.

1// Bad
2import { MyComponent } from "@/devlink";
3
4// Good
5import { MyComponent } from "@/devlink/MyComponent";
Built with