Component Architecture

Principles for designing components in Webflow to ensure clean export

You can design components in Webflow the same way you normally would, using classes, most elements, and interactions. DevLink takes care of turning those designs into React components.

By following these guidelines, you’ll make sure your components export cleanly and behave as expected in your React app.

Supported elements

The following Webflow elements are supported for export through DevLink:

- Container
- Div Block
- Section
- Grid
- Columns
- List / List Item
- Map
- Navbar
- Slider
- Tabs
Ecommerce and CMS-specific elements are not supported

Ecommerce and CMS-specific elements aren’t supported in Exported Components.

Use DevLink Slots instead of Component Slots

Component slots aren’t supported in Exported Components. Please see documentation on how to use DevLink Slots instead.

Component architecture

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:

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)

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)

Forms

Design forms so engineers can choose between two predictable build paths:

Export a whole Webflow form for simple, lightweight flows when you don’t need controlled inputs or complex validation.

Add a runtime prop to the Form in Webflow so apps can pass handlers like onSubmit.

In React, treat the exported form as an uncontrolled form: prevent default, read values via FormData, and post to your endpoint. If your handler throws, render the form’s Error state; otherwise show Success and map these directly to Webflow’s built-in states.

Example

This example uses a whole Webflow form exported as one React component. The form stays visually identical to your Webflow design, but in your React code you pass it a runtime prop called formProps. That prop lets you hook into the form’s submit event.

newsletter-signup-form.jsx
1import { NewsletterSignupForm } from "@/devlink/NewsletterSignupForm";
2
3export function FooterSignup() {
4 return (
5 <NewsletterSignupForm
6 // Runtime prop
7 formProps={{
8 onSubmit: async (e: React.FormEvent<HTMLFormElement>) => {
9 e.preventDefault();
10 const res = await fetch("/newsletter-signup", {
11 method: "POST",
12 body: new FormData(e.currentTarget),
13 });
14 if (!res.ok) throw new Error("Submit failed"); // triggers Error state
15 },
16 }}
17 />
18 );
19}
  • NewsletterSignupForm is your exported Webflow form, now usable as a React component. It already includes the fields, labels, success message, and error message you styled in Webflow.
  • formProps is a runtime prop that lets you pass an object that can include handlers like onSubmit.
  • onSubmit function
    • The handler prevents the browser’s default form submission.
    • It collects the field values with new FormData(e.currentTarget).
    • It posts that data to your app’s backend (here: /newsletter-signup).
    • If the request fails, it throws an error. That error tells the form to switch to its Error state. If the request succeeds, the form automatically shows its Success state.
  • Success/Error states These are the states you set up visually in Webflow. The exported component just switches between them depending on whether your submit handler finishes or throws.

Instead of exporting a whole form, you can export individual inputs and buttons and build your own <form> in React. This gives you full control over:

  • Validation (for example, with react-hook-form + Zod)
  • Error messages (announced with aria-invalid and aria-describedby)
  • Submission state (disable the button while sending, show a spinner)
  • Success handling (show a toast, banner, or redirect)

This approach keeps the Webflow design but lets you manage all behavior in React.

validate-signup-form.jsx
1import { useForm } from "react-hook-form";
2import { EmailInput } from "@/devlink/EmailInput";
3import { SubmitButton } from "@/devlink/SubmitButton";
4
5export function SignupForm() {
6 const { register, handleSubmit, formState } = useForm<{ email: string }>();
7
8 const onSubmit = async (data: { email: string }) => {
9 await fetch("/newsletter-signup", {
10 method: "POST",
11 body: new URLSearchParams(data),
12 });
13 // Show your own success UI here
14 };
15
16 return (
17 <form onSubmit={handleSubmit(onSubmit)} noValidate>
18 <EmailInput
19 inputProps={{
20 ...register("email", { required: true }),
21 type: "email",
22 placeholder: "you@example.com",
23 "aria-invalid": formState.errors.email ? true : undefined,
24 }}
25 />
26
27 {formState.errors.email && (
28 <p role="alert">Please enter a valid email.</p>
29 )}
30
31 <SubmitButton />
32 </form>
33 );
34}

In this example, you export the EmailInput and SubmitButton elements from Webflow. You then assemble them in your own <form> in React. You use react-hook-form to handle the form’s state and validation.

  • React manages validation and errors with react-hook-form.
  • An error message is displayed and announced when validation fails.

Success handling is left to the developer (toast, banner, redirect, etc.).

Custom Code

DevLink doesn’t export site or page-level custom code (e.g. code added in a page’s <head> or <body>). It also doesn’t preserve plain class selectors because DevLink applies unique namespaces to every component.

If you need custom CSS or JS with a DevLink component, add it using a Code Embed element placed inside the component.

Adding custom CSS

  1. Add a Code Embed inside your Webflow component.
  2. Wrap your CSS in <style> tags.
  3. Target DevLink’s namespaced classes with an attribute selector:
my-custom-code-embed.css
1<style>
2[class*="MyComponent_my-class__"] {
3 /* your styles */
4}
5</style>

Adding custom JavaScript

The Code Embed element also supports <script>. Place scripts here only if they’re specific to the component. For logic, prefer handling interactions in your React/JS app.

Repeatable lists

When rendering lists of data in an exported component, split the implementation into:

  • Container component defines the overall list structure and exposes a slot property for content.

  • Item component defines how each item is rendered.

This pattern keeps layouts reusable, rows portable, and responsibilities clear.

Example: User table

Create the following components in Webflow:

  • UserTable — table structure with a DevLink slot property named tableContent.
  • UserTableRow — a row layout for a single user.

In React, you can use the UserTable component to render the outer table and then pass it a list of UserTableRow components (hydrated with your own data) with the tableContent prop.

user-dashboard.jsx
1import { UserTable } from "@/devlink/UserTable";
2import { UserTableRow } from "@/devlink/UserTableRow";
3import { useQuery } from "react-query";
4
5// Replace with your real data source
6const fetchUsers = () =>
7 Promise.resolve([
8 {
9 id: 1,
10 image: "/sophie.png",
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: "/andy.png",
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({ queryKey: ["users"], queryFn: fetchUsers });
32
33 return (
34 <UserTable
35 tableContent={
36 <>
37 {(users ?? []).map(({ active, id, ...user }) => (
38 <UserTableRow
39 {...user}
40 key={id}
41 greenBadgeVisibility={active}
42 redBadgeVisibility={!active}
43 greenBadgeText="ACTIVE"
44 redBadgeText="INACTIVE"
45 />
46 ))}
47 </>
48 }
49 />
50 );
51};
52
53export default Users;

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.