# Vev Developer
> Building no-code with code
import Table from '../../components/Table';
## Tracking and Analytics
[Vev](https://vev.design) takes an agnostic approach to analytics and *does not* directly track visitors on Vev-created content or pages. However, we offer tools and methods to help you implement your preferred tracking solutions. This can be done through our [hosting integrations](https://www.vev.design/features/integrations/) or by using *event tracking* as described in this article.
:::info
[Vev's privacy policy](https://www.vev.design/legal/privacy-policy/): Vev does not have a direct relationship with Customers’ End Users. A “Customer End User” is an individual that provides their Personal Information to our Customers. We do not control the purposes nor the means by which this Personal Information is collected, and we are not in a direct relationship with Customer End Users. As described in.
:::
### Integration
To implement event-based tracking, you need to listen for all Vev tracking events. This is done by listening for the `vev.track` event on the `window` object. Once captured, you can forward the event data to your preferred tracking tool, internal data repository, or any similar system.
```js
window.addEventListener('vev.track', (e) => {
const event = e.detail;
console.log('event', event);
});
```
Example: Adobe Analytics Implementation
```js
window.addEventListener('vev.track', (e) => {
const event = e.detail.data;
// Example: Send to Adobe Analytics
alloy('sendEvent', {
type: event.type,
data: {
...event.data,
vevProjectKey: event.metaData.projectKey,
vevPageKey: event.metaData.pageKey,
},
});
});
```
#### Event data structure
Vev offers two methods for creating tracking events: standard events and interaction-based event tracking. The `data` object is unique to each event type, while `metaData` remains consistent across all events.
(
<>
projectKey string
projectName string
breakpoint string
>
),
],
[
{ name: 'VEV_PAGE_LOAD', type: 'string' },
'Dispatches when the page load',
() => (
<>
pageKey string
pageName string
projectKey string
breakpoint string
>
),
],
[
{ name: 'VEV_LINK_CLICK', type: 'string' },
'Dispatches when an external link is clicked',
() => (
<>
url string
>
),
],
[
{ name: 'VEV_VIDEO_PLAY', type: 'string' },
'Dispatches when an video is started',
() => (
<>
videoUrl string
totalPlayTime number (seconds)
percentagePlayed number (percentage)
>
),
],
[
{ name: 'VEV_VIDEO_STOP', type: 'string' },
'Dispatches when an video is stopped or paused',
() => (
<>
videoUrl string
totalPlayTime number (seconds)
percentagePlayed number (percentage)
>
),
],
[
{ name: 'VEV_VIDEO_END', type: 'string' },
'Dispatches when an video is fully played.',
() => (
<>
videoUrl string
totalPlayTime number (seconds)
percentagePlayed number (percentage)
>
),
],
[
{ name: 'VEV_VIDEO_PROGRESS', type: 'string' },
'Dispatches the progress of a video in seconds.',
() => (
<>
videoUrl string
videoName string
progress number (seconds)
totalPlayTime number (seconds)
percentagePlayed number (percentage)
>
),
],
]}
/>
### Custom events
Custom events can be added using [interactions](https://help.vev.design/en/articles/8449049-how-to-use-interactions). For these events, the `type` and `data` are defined by the designer within the design editor. Custom events can be linked to various interaction triggers, such as `onVisible`, `onScroll`, and more. They are typically used for tracking call-to-action (CTA) interactions.

## Components
### Image
The `Image` component can be used with the `type: image` schema field.
#### Example
```jsx
import React from 'react';
import { registerVevComponent, Image } from '@vev/react';
const MyComponent = (props) => ;
registerVevComponent(MyComponent, {
name: 'My component',
props: [
{
name: 'image',
type: 'image',
},
],
});
export default MyComponent;
```
#### Interface
```ts
type Props = {
className?: string;
sizes?: [number, number][];
src?: string | { key: string };
style?: { [attr: string]: number | string };
};
```
### Link
The `Link` component can be used together with the `type: link` schema field.
##### Example
```jsx
import React from 'react';
import { registerVevComponent, Link } from '@vev/react';
const MyComponent = (props) => ;
registerVevComponent(MyComponent, {
name: 'My component',
props: [
{
name: 'link',
type: 'link',
},
],
});
export default MyComponent;
```
#### Interface
```ts
type Props = {
/**
* Preset for what linkType the field should be.
* 0: Page, 1: Element, 2: External link, 3: Email, 4: Phone.
*/
mode: 0 | 2 | 3 | 4;
href?: string;
page?: string;
target?: boolean;
phone?: string;
email?: string;
};
```
### Render
The `Render` component is used to render a [Vev](https://www.vev.design/) project in your React application.
The component supports [server side rendering](https://web.dev/rendering-on-the-web/) using [Suspense](https://react.dev/reference/react/Suspense),
which means it will deliver static HTML on the server (if the app is built for server side rendering using Suspense) and hydrate on the client. This
is also compatible with [NextJS](https://nextjs.org/) if you are using the [app routing feature](https://beta.nextjs.org/docs/app-directory-roadmap).
:::caution
To use this component with [NextJS](https://nextjs.org/) you have to [opt-in to the app directory routing](https://beta.nextjs.org/docs/installation).
:::
This component will also work in a traditional React application, but the component will only render client side.
##### Example
```jsx
import React from 'react';
import { Render as VevRender } from '@vev/react';
const MyComponent = (props) => {
return ;
};
export default MyComponent;
```
:::info
To find the `projectKey` and `pageKey` look at your [Vev Editor](https://editor.vev.design) URL:
```
https://editor.vev.design/edit/[projectKey]/[pageKey]
```
:::
#### Interface
```ts
type Props = {
projectKey: string;
pageKey: string;
noCache: boolean;
fallback: React.ReactNode; // Render a fallback component when loading
};
```
## Hooks
These hooks offer different way to interact with the Vev Editor.
### useIntersection
Tracks the portion of an element that intersects with the visible area of the screen (the viewport).
```ts
const el_intersection: false | IntersectionObserverEntry = useIntersection(ref: React.RefObject, options?: IObserverOptions);
```
##### Interface
```ts
interface IntersectionObserverEntry {
/**
* Properties of the tracked reference element
*/
boundingClientRect: DOMRectReadOnly;
/**
* Visible amount of the tracked reference element
*/
intersectionRatio: number;
/**
* Properties of only the visible portion of the tracked reference element (a subset of `boundingClientRect`)
*/
intersectionRect: DOMRectReadOnly;
isIntersecting: boolean;
rootBounds: DOMRectReadOnly | null;
target: Element;
time: number;
}
interface IObserverOptions {
/**
* Number of times the hook updates as the tracked element enters the visible screen. Defaults to 1.
*/
steps?: number;
/**
* Decimal values indicating the intersection percentage at which the hook updates.
*/
threshold?: number[];
}
```
It is not possible to define both a step and a threshold. Define one at most.
##### Example
Display the percentage of a widget that intersects with the viewport. Update this value 10 times over the course of the
widget’s entrance into the viewport.
```tsx
import { useRef } from 'react';
import { useIntersection } from '@vev/react';
export default function () {
const widgetReference = useRef(null);
const intersection = useIntersection(widgetReference, { steps: 10 });
return (
{intersection.intersectionRatio * 100}
);
}
```
### useDevice
Vev projects can be custom-designed for various presentation modes such as desktop, tablet, or mobile. `useDevice()`
returns the currently active mode `('desktop' | 'tablet' | 'mobile')`.
##### Usage
```ts
const device: string = useDevice();
```
##### Example
```tsx
import { useDevice } from '@vev/react';
export default function () {
const device: 'desktop' | 'tablet' | 'mobile' = useDevice();
return
Device: {device}
;
}
```
### useEditorState
For better widget usability, consider changing the state of your widget depending on which mode the user is in. For
example, play a video when in preview mode, but stop it when in editor mode. Or, if the user is changing the background
color of a hidden dropdown in your widget, reveal that dropdown when its background colour property is being changed.
`useEditorState()` returns the following:
* `disabled: boolean` - true when in editor mode, false when in preview mode.
* `rule: string` - tells which CSS rule the user is editing. Sets to ‘host’ when no rule is being edited .
* `selected: boolean` - true when the widget is selected in editor mode.
##### Example
```tsx
import { useEditorState } from '@vev/react';
import { useState, useEffect, useRef } from 'react';
export default function ({ url }: Props) {
const { disabled } = useEditorState();
const videoReference = useRef(null);
// this function runs on every value change of 'disabled'
useEffect(() => {
// If disabled, pause video
if (disabled) {
videoReference.current.pause();
} else {
videoReference.current.play();
}
}, [disabled]);
return ;
}
```

### useFrame
Triggers a callback function every animation frame. Optionally pass a `time` parameter in your callback function to
track callback start time. `useFrame()` calls are paused when running in background tabs.
In the past, `setTimeout` and `setInterval` were commonly used to iterate a block of code for animations. Using these
causes the browser to paint frames quicker than most screen refresh rates of 60 FPS. This results in skipped frames and
unnecessary computation, making snappy animations. `useFrame()` executes the passed call back function more efficiently by
eliminating unnecessary repaints and bundling multiple animations into a single repaint cycle.
##### Usage
```typescript
useFrame(callback: (time?: number), deps?: readonly any[]): () => void;
```
##### Example
Sliding an element to the right by 500px.
```tsx
import { useFrame } from '@vev/react';
export default function () {
let startTime = null;
useFrame((time) => {
if (!startTime) startTime = time;
const progress = time - startTime;
const boxElement = document.getElementById('SomeElementYouWantToAnimate');
if (progress < 5000) {
boxElement.style.left = progress / 10 + 'px';
}
});
return (
);
}
```

### useIcon
Watches and returns information about an icon given its icon key. The icon key can be created/found in the widget’s
manifest file.
For SVG icons the following information is returned:
* `width` - the width of the SVG viewbox
* `height` - the height of the SVG viewbox
* `path` - the SVG path to render the icon
##### Usage
```typescript
const iconInfo: [ number, number, string | string[] ] = useIcon( iconKey: string );
```
##### Example
Render an icon
```tsx
import { useIcon } from '@vev/react';
export default function () {
const [width, height, ...path] = useIcon('smiley-icon');
return (
);
}
```
### useImage
Watches and returns information about an image.
##### Usage
```typescript
const imageData: ImageModel = useImage(imageKey: string);
```
##### Interfaces
```typescript
interface ImageModel {
meta?: {
description?: string;
alt?: string;
photographer: string;
width?: number;
height?: number;
};
src: string;
srcset: [string, number][];
key: string;
}
```
### useInterval
Run code at specified time intervals. Code will continue to run after every interval lapse until interval is cleared or
window is closed.
##### Example
```tsx
import { useInterval } from '@vev/react';
import { useState } from 'react';
export default function () {
const [count, setCount] = useState(0);
// Increase counter every second
useInterval(() => setCount(count + 1), 1000);
return
{count}
;
}
```

### useMenu
In the editor, the designer can build custom menus. To use these menus in a widget, use the useMenu hook.
##### Usage
```typescript
const menuChildren: IMenu = useMenu(menuKey?: string);
//if no menu key is defined, the primary menu will be used.
```
##### Interfaces
```typescript
interface IMenu {
title: string;
children: IMenuItem[];
}
interface IMenuItem {
key: string;
title: string;
link: IPageLink | IPhoneLink | IEmailLink | IExternalLink | IWidgetLink;
children?: IMenuItem[];
}
```
##### Example
Create a simple menu.
```tsx
import { Link, registerVevComponent, useMenu } from '@vev/react';
type Props = {
menuselect: string;
};
const MenuExample = ({ menuselect }: Props) => {
const menu = useMenu(menuselect);
if (!menu) {
return
No menu items
;
}
return (
{menu.title}
{menu.children.map((child) => {
return (
{child.title}
);
})}
);
};
registerVevComponent(MenuExample, {
name: 'MenuExample',
props: [
{
name: 'menuselect',
type: 'select',
options: {
display: 'autocomplete',
items: (context: any) => {
if (context.menus) {
return Object.keys(context.menus).map((menuKey) => {
return {
label: context.menus[menuKey].title,
value: context.menus[menuKey].key,
};
});
}
return [];
},
},
},
],
});
export default MenuExample;
```

### useModel
Watches and returns the content model using the widget key. Content model refers to the intrinsic properties of the
widget like element ID, classname, icon’s, and form-field values.
##### Usage
```ts
const widgetModel: IContent = useModel(key? string);
// empty key parameter returns the model of the widget you are currently coding.
```
##### IContent type interface
```ts
interface IContent {
/**
* Widget's element ID
*/
key: string;
/**
* Widget's classname
*/
type?: string;
style?: {}; // doesn't even work
/**
* Contents of the widget's formfield
*/
content?: {};
/**
* Classname
*/
cl?: string;
pin?: boolean; // doesn't even work
html?: boolean; // DONT DOCUMENT
/**
* Wcons set on the widget
*/
icons?: { [key: string]: IShape };
actions?: string[];
children?: string[];
}
```
##### Example
Observe another widget’s form-field value.
```tsx
import { useModel } from '@vev/react';
export default function ({ trackedWidget }: Props) {
const usemodel = useModel(trackedWidget.key);
return
{usemodel.content.text}
;
}
```

### useRoute
Watches and returns information about the current page route.
##### Usage
```ts
const { pageKey, path } = useRoute();
```
### useScrollTop
Track how much the page has scrolled down relative to the top of the view port.
##### Usage
```ts
const scrollTop : number = useScrollTop(asPercentage?:boolean);
```
##### Example
```tsx
import { useScrollTop } from '@vev/react';
export default function () {
return (
Scroll Top (pixels): {useScrollTop()}
Scroll Top (percentage): {useScrollTop(true)}
);
}
```
### useSize
Detect when an element resizes. Returns width and height of the element passed into the hook parameter.
##### Usage
```ts
const useSizeHook: { width: number; height: number } = useSize(React.RefObject);
```
##### Example
```tsx
import { useSize } from '@vev/react';
import { useRef } from 'react';
export default function () {
const elementReference = useRef(null);
const { width, height } = useSize(elementReference);
return (
{width} x {height}
);
}
```

### usePages
Returns all the projects pages and the root directory of published project.
##### Usage
```ts
const [pages, rootDir] = usePages();
```
##### Example
```tsx
import { usePages } from '@vev/react';
export default function() {
const [pages, rootDir] = usePages();
return (
< /div>
);
};
```
### useViewport
Watches and returns the viewport `width`, `height`, and `scrollHeight`. The viewport is the user's visible area of a
webpage and can vary based on device sizes.
##### Usage
```ts
const { width, height, scrollHeight } = useViewport();
```
##### Example
```tsx
import { useViewport } from '@vev/react';
export default function ({ text }: Props) {
const [count, setCount] = useState(0);
const viewport = useViewport();
const { disabled } = useEditorState();
function test() {
console.log('viewport:', viewport);
}
return (
Width: {viewport.width}
Height: {viewport.width}
ScrollHeight: {viewport.scrollHeight}
);
}
```

### useVisible
Detect if an element is visible in the viewport.
##### Usage
```ts
const isVisible: boolean = useVisible(ref: React.RefObject, object?: IVisibleOptions);
```
##### IVisibleOptions Interface
```ts
interface IVisibleOptions {
offSetTop?: number | string;
offSetBottom?: number | string;
}
```
##### Example
```tsx
import { useVisible } from '@vev/react';
export function VisibleTest() {
const elementReference = useRef(null);
const visible = useVisible(elementReference);
return ;
}
```
### useVevEvent
Use Vev events dispatched from Interactions.
To use this hook, remember to also add `interactions` to `registerVevComponent`.
##### Usage
```ts
useVevEvent(string, () => {});
```
##### Example
```tsx
import { useVevEvent } from '@vev/react';
const YouTube = () => {
const playerRef = React.useRef();
useVevEvent('PLAY_VIDEO', () => playerRef.current?.playVideo());
// ...,
return ;
};
registerVevComponent(Youtube, {
name: 'YouTube',
// ...,
interactions: [
{
type: 'PLAY_VIDEO',
description: 'Start playing video',
},
],
});
```
### useDispatchVevEvent
Emits Vev events from your component which can be used to trigger interactions.
##### Usage
```ts
const dispatch = useDispatchVevEvent();
dispatch('EVENT');
```
##### Example
```tsx
import { useDispatchVevEvent } from '@vev/react';
const YouTube = () => {
const playerRef = React.useRef();
const dispatch = useDispatchVevEvent();
React.useEffect(() => {
// ...
function onPlayerStateChange(event) {
switch (event.data) {
case YT.PlayerState.PLAYING:
dispatch('VIDEO_PLAYING');
}
}
}, []);
// ...
return ;
};
registerVevComponent(Youtube, {
name: 'YouTube',
// ...
events: [
{
type: 'VIDEO_PLAYING',
description: 'Video starts playing',
},
],
});
```
### useTracking
Dispatches a `vev.track` event, that can be used for [tracking](/tracking).
##### Usage
```ts
const track = useTracking(disabled: boolean);
dispatch('MY_EVENT_NAME', {
customValue: 'value',
})
```
##### Example
```tsx
import { useTracking } from '@vev/react';
const MyComponent = () => {
const track = useTracking();
const handler = React.useCallback(() => {
track('MY_HANDLER_EVENT', {
data: 'handler-clicked',
});
// ...
}, []);
// ...
};
```
### useVariable
Watches and returns the variable using the variable key.
Use in conjunction with the [variable](/react/vev-props#variable) field to get the variable key.
##### Usage
```ts
const variable = useVariable(variableKey);
```
##### Example
```tsx
import { useVariable } from '@vev/react';
const MyComponent = ({ variableKey }: Props) => {
const variable = useVariable(variableKey);
return (
Value is: {variable?.value}
);
};
```
### useSetVariable
Dispatch changes to a variable.
Use in conjunction with the [variable](/react/vev-props#variable) field to get the variable key.
##### Usage
```ts
const setVariable = useSetVariable(variableKey);
setVariable(variableKey, {value: 100, unit: 'px'});
```
##### Example
```tsx
import { useSetVariable } from '@vev/react';
const MyComponent = ({ variableKey }: Props) => {
const setVariable = useSetVariable();
return (
);
};
```
import Table from '../../components/Table';
## Children
You can add content children in the [Design Editor](https://editor.vev.design/), using the children property in [Register Vev component](/react/register-vev-component).
`children` is an object containing the following properties.
Example of the content children in use is the Slider component.
### Render content children
When content children is added in `vevRegisterComponent`, you component will receive a `children` prop.
This prop is an array of `content keys` for the content added as children in the Design Editor.
To render the content, you can use thee `WidgetNode` component from `@vev/react`.
```jsx title="slideshow.js"
import React from 'react';
import { registerVevComponent, WidgetNode } from '@vev/react';
import styles from './slideshow.module.scss';
const Slideshow = ({ children = [] }) => {
return (
{children.map((child) => (
))}
);
};
registerVevComponent(Slideshow, {
name: 'My awesome component',
children: {
name: 'Slide',
icon: './slide-icon.png',
},
});
export default Slideshow;
```
import Table from '../../components/Table';
## Editable CSS
All styles can be made editable in the [Design Editor](https://editor.vev.design/), using the editableCSS property in [Register Vev component](/react/register-vev-component).
`editableCSS` is an array containing the following properties.
Any of these CSS properties as a string:{' '}
font-family, background, color, margin, padding, border, border-radius, opacity, filter
.
,
],
]}
/>
### Example
```jsx title="my-component.js"
import React from 'react';
import { registerVevComponent } from '@vev/react';
import styles from './my-component.module.scss';
const MyComponent = ({ title }) => {
return (
{title}
);
};
registerVevComponent(MyComponent, {
name: 'My awesome component',
editableCSS: [
{
selector: styles.wrapper,
properties: ['background', 'border-radius'],
},
{
selector: styles.title,
properties: ['color'],
},
],
});
export default MyComponent;
```
import Table from '../../components/Table';
## Interactions and Events
Interaction and events facilitate dynamic communication among components, central to an event-driven architecture. This approach empowers components not only to respond to external events but also to broadcast their own, enabling other components to react accordingly. This provides you the capability to design intricate components that are both interactive and responsive to interactions from others.
### Vev Event
The events itself is identical for interactions and events. These events can be defined
under `interactions` and `events` in the [registerVevComponent](/react/vev-props#registervevcomponent) function as an array of `VevEvent`.
(
<>
Array of Vev props Vev props documentation
>
),
],
]}
/>
This events are dispatched as [custom events](https://developer.mozilla.org/en-US/docs/Web/Events/Creating_and_triggering_events) under the `@@vev` namespace.
### Interactions
Interactions is something your component can do, for example an element can be hidden.
But it is also possible for elements to have custom actions, like a video player can play or pause.
To use **interaction** in your code, you have to add `interactions` to `registerVevComponent` and then
use the [useVevEvent](/react/hooks#usevevevent) hook in your code.

### Events
Events are things that happens, like a button is clicked (`On click`) or a page is scrolled (`On scroll`).
But elements can also dispatch their custom events, for example a video player can dispatch a `On play` event.
To use events in your code, you have to add `events` to `registerVevComponent` and then use the
[useDispatchVevEvent](/react/hooks#usedispatchvevevent) hook to dispatch them.

### Example
An example for this is the [YouTube component](https://github.com/vev-design/vev-components/blob/main/youtube/src/Youtube.tsx), as demonstrated in
[this Vev project](https://editor.vev.design/edit/Nu5qTeDM6I/pY0w7AF3hgZ). This component includes the following interactions and events that can be
leveraged by the design editor.
Interactions
Play
Pause
Toogle play
Restart
Mute
Unmute
Toggle sound
Events {' '}
On play
On pause
On end
On play time
#### Code
```tsx
enum YoutubeInteraction {
play = 'play',
pause = 'pause',
}
enum YoutubeEvent {
onPlay = 'onPlay',
onPause = 'onPause',
}
const YouTube = ({ videoId }) => {
const ref = useRef(null);
const dispatch = useDispatchVevEvent();
const src = 'https://www.youtube.com/embed/' + videoId;
// Listen to events from Vev adn dispatch to YouTube
useVevEvent(YoutubeInteraction.play, () => playerRef.current?.playVideo());
useVevEvent(YoutubeInteraction.pause, () => playerRef.current?.pauseVideo());
useEffect(() => {
playerRef.current = new YT.Player(ref.current, {
events: {
onStateChange: onPlayerStateChange,
},
});
// Listen to events from YouTube and dispatch to Vev
function onPlayerStateChange(event) {
switch (event.data) {
case YT.PlayerState.PLAYING:
dispatch(YoutubeEvent.onPlay);
break;
case YT.PlayerState.PAUSED:
dispatch(YoutubeEvent.onPause);
break;
}
}
}, []);
return (
);
};
registerVevComponent(Youtube, {
name: 'YouTube',
// ...,
events: [
{
type: YoutubeEvent.onPlay,
description: 'On play',
},
{
type: YoutubeEvent.onPause,
description: 'On pause',
},
],
interactions: [
{
type: YoutubeInteraction.play,
description: 'Play',
},
{
type: YoutubeInteraction.pause,
description: 'Pause',
},
],
});
```
This is a non-working simplified version of the code, find [full code on GitHub](https://github.com/vev-design/vev-components/blob/main/youtube/src/Youtube.tsx)
[Example of the YouTube component in the Vev Design editor](https://editor.vev.design/edit/Nu5qTeDM6I/pY0w7AF3hgZ)
import Table from '../../components/Table';
## Register Vev component
Install the React package into an existing React project:
```
npm i @vev/react --save
```
If you are creating a new project, you can also use the [vev create command](/cli/commands#create) to get a boilerplate.
### Register
In order to run your components, you first need to register them using the `registerVevComponent` function. In your `./src` directory you can add your first component.
Components can be initialized anywhere in your project inside a `./src` folder, but all components need to be exported.
```js
registerVevComponent(Component, Config);
```
#### Config
Possible config parameters for `registerVevComponent`:
(
The type of the component in the Vev. Can be standard, section,{' '}
action or both.
Default is standard.
),
],
[
{ name: 'size', type: 'object' },
() => (
<>
The default size of the component. width: number, height: number;
Default is width: 100px and height 100px.
>
),
],
[
{
name: 'props',
type: 'array',
},
() => (
<>
Array of Vev props Vev props documentation
>
),
],
[
{
name: 'editableCSS',
type: 'array',
},
() => (
<>
Define CSS properties that will be editable in the Deisgn Editor.
Editable CSS documentation
>
),
],
[
{ name: 'component', type: 'ReactNode' },
'To render a custom component in the editing panel.',
],
[
{ name: 'panelType', type: 'string' },
() => (
<>
The type of the editing panel. Can be inline.
Default is not set (side panel).
>
),
],
[
{
name: 'children',
type: 'object',
},
() => (
name: string, icon: string;
To render Vev content as children. Children documenation
),
],
[
{
name: 'interactions',
type: 'array',
},
() => (
<>
Array of Vev events Interactions documentation
>
),
],
[
{
name: 'events',
type: 'array',
},
() => (
<>
Array of Vev events Events documentation
>
),
],
]}
/>
### Examples
#### Simple (JavaScript)
The only required field is the `React component` and the field name.
```tsx title="my-component.js"
import React from 'react';
import { registerVevComponent } from '@vev/react';
const MyComponent = () => {
return
Hello, Vev
;
};
registerVevComponent(MyComponent, {
name: 'My awesome component',
});
export default MyComponent;
```
#### Advanced (TypeScript)
A more complex example listing a set of products.
```tsx title="my-component.tsx"
import React from 'react';
import { registerVevComponent, Image } from '@vev/react';
import styles from './my-component.module.scss';
type Props = {
title: string;
image: {
src: string;
key: string;
};
products: {
title: string;
price: number;
image: {
src: string;
key: string;
};
}[];
};
const MyComponent = ({ title, image, products = [] }: Props) => {
return (
{title}
{products.map((product, i) => (
{product.title}
{product.price}
))}
);
};
registerVevComponent(MyComponent, {
name: 'My awesome component',
type: 'section',
props: [
{
name: 'title',
type: 'string',
},
{
name: 'image',
type: 'image',
},
{
name: 'products',
type: 'array',
of: [
{ name: 'name', type: 'string' },
{ name: 'price', type: 'number' },
{ name: 'image', type: 'image' },
],
},
],
editableCSS: [
{
selector: styles.wrapper,
properties: ['background-color'],
},
{
selector: styles.productBorder,
properties: ['border-color'],
},
],
});
export default MyComponent;
```
import Table from '../../components/Table';
## Props
Props are properties sent from the [Design Editor](https://editor.vev.design/) to your component(s).
### Base
All props share these common properties:
(
<>
Type of the prop. Can be one of the following:
string | number | boolean | image | object | array | select | link | upload | menu |
layout
.
>
),
],
[
{ name: 'title', type: 'string' },
'Title of the prop visible in the edtior. If not set, a sentence case of the name will be used',
],
[{ name: 'description', type: 'string' }, 'Description of the prop visible in the editor.'],
[{ name: 'component', type: 'ReactNode' }, 'Replaces the field with a custom React component.'],
[
{
name: 'initialValue',
type: 'JSON',
},
() => (
<>
The initial value for the field. Have to match the type of the field. Can also be a
function or async function returning the value. Function takes one argument
with the type
context.
>
),
],
[
{
name: 'validate',
type: 'boolean | function',
},
() => (
<>
Can either be boolean or a function returning boolean. The first
argument of the function is the value of the field and the second is the{' '}
context of the entire form.
>
),
],
[
{
name: 'hidden',
type: 'boolean | function',
},
() => (
<>
Hide/show field. Can either be boolean or a function returning{' '}
boolean. The first argument of the function is the value of the
field and the second is the context of the entire
form. Can be used to toggle visible based on values from other fields.
>
),
],
[
{
name: 'onChange',
type: ' function',
},
() => (
<>
A custom onChange for the field. The first argument of the function is the{' '}
value of the field and the second is the{' '}
context of the entire form.
>
),
],
[
{ name: 'storage', type: 'string' },
() => (
<>
Select where to store values for the component (like API keys etc.). Can be{' '}
project | workspace | account.{' '}
>
),
],
]}
/>
### String
A field type for string.
```ts
type: "string",
```
Options:
(
<>
Can be 'text' | 'date' | 'email' | 'url' | 'password'. Default is{' '}
text.
>
),
],
[
{ name: 'multiline', type: 'boolean | number' },
() => (
<>
Select if the field should be multiline or not. Can either be boolean or a{' '}
number representing the lines of the text the field should include.
>
),
],
[{ name: 'minLength', type: 'number' }, () => <>The minimum characters for the field.>],
[{ name: 'maxLength', type: 'number' }, () => <>The maximum characters for the field.>],
]}
/>
**Example:**
```js
registerVevComponent(MyComponent, {
name: 'My component',
props: [
{
name: 'title',
type: 'string',
options: {
minLength: 10,
maxLength: 20,
},
},
],
});
```
### Number
A field type for number.
```ts
type: "number",
```
<>The minimum number for the field.>],
[{ name: 'max', type: 'number' }, () => <>The maximum number for the field.>],
[
{ name: 'format', type: 'string' },
() => (
<>
This will appear as a suffix for the number. Can be '%' | 'px' | 'deg'.
>
),
],
[
{ name: 'display', type: 'string' },
() => (
<>
The appearance of the field. Can be 'input' | 'slider'. Used for displaying a
slider instead of a number field.
Default is: input.
>
),
],
[
{ name: 'scale', type: 'number' },
'Set a custom scale for the field. If you want percent from 0-1 set scale to 100',
],
]}
/>
**Example:**
```js
registerVevComponent(MyComponent, {
name: 'My component',
props: [
{
name: 'price',
type: 'number',
options: {
min: 100,
max: 1000,
},
},
],
});
```
### Boolean
A field type for boolean. This will display a toggle switch in the form.
```ts
type: "boolean",
```
**Example:**
```js
registerVevComponent(MyComponent, {
name: 'My component',
props: [
{
name: 'showOnMobile',
type: 'boolean',
},
],
});
```
### Select
Can be used to display either radio buttons, a dropdown or multiselect.
```ts
type: "select",
```
Options:
(
<>
An array of options in this format: label: string, value: string.
Can also be async.
>
),
],
[
{ name: 'display', type: 'string' },
() => (
<>
{' '}
Can be: 'checkbox' | 'radio' | 'dropdown' | 'autocomplete'.
Default is dropdown.
>
),
],
[
{ name: 'multiselect', type: 'boolean' },
'If it should be possible to select multiple values.',
],
]}
/>
**Example:**
```js
registerVevComponent(MyComponent, {
name: 'My component',
props: [
{
name: 'selectFruits',
type: 'select',
options: {
display: 'radio',
items: [
{ label: 'Apple', value: 'apple' },
{ label: 'Orange', value: 'orange' },
{ label: 'Banana', value: 'banana' },
],
},
},
],
});
```
### Image
A field type for `image`. This will display an image upload field in the editor.
```ts
type: "image",
```
**Example:**
```js
registerVevComponent(MyComponent, {
name: 'My component',
props: [
{
name: 'image',
type: 'image',
},
],
});
```
### Link
A field for `link`. This will display a link field in the editor.
This will give a search field for internal links. If an internal link
is not selected it will behave like an external link.
```ts
type: "link",
```
```js
registerVevComponent(MyComponent, {
name: 'My component',
props: [
{
name: 'link',
type: 'link',
},
],
});
```
### Object
A field type for `object`. This can be used to display fields in a group.
All the Vev props can be used as `fields`.
```ts
type: "object",
fields: VevProps[],
```
##### Example
```js
registerVevComponent(MyComponent, {
name: 'My component',
props: [
{
name: 'header',
type: 'object',
fields: [
{
name: 'title',
type: 'string',
},
{ name: 'subtitle', type: 'string' },
{ name: 'image', type: 'image' },
],
},
],
});
```
### Array
A field type for `array`. This can be used to make a list of editable properties.
All of the Vev props can be used as `of`.
```ts
type: "array",
of: VevProps[],
```
**Example:**
To allow either strings or numbers in the array, you can use the `of` option.
```js
registerVevComponent(MyComponent, {
name: 'My component',
props: [
{
name: 'products',
type: 'array',
of: [
{
name: 'title',
type: 'string',
},
{ name: 'price', type: 'number' },
],
},
],
});
```
If you wish to have a more complex array, you can use the `object` type as `of`.
```js
registerVevComponent(MyComponent, {
name: 'My component',
props: [
{
name: 'products',
type: 'array',
of: [
{
name: 'item',
type: 'object',
fields: [
{
name: 'title',
type: 'string',
},
{ name: 'price', type: 'number' },
],
},
],
},
],
});
```
##### List preview
Every element in an array field, can have custom list preview. If not defined, the Editor will try to fined the fields from the data.
(
<>
can either be an object or a function which returns an object:{' '}
title: string, image: string.
>
),
],
]}
/>
##### Example
```js
{
name: "products",
type: "array",
of: [
{
name: "item",
type: "object",
preview(value) {
return {
title: value.title,
image: value.image,
};
},
fields: [
{
name: "title",
type: "string",
},
{ name: "image", type: "image" },
],
},
],
}
```
### Layout
A field to be used for layout.
```ts
type: "layout",
fields: VevProps[],
```
Options:
(
<>
Can be row | column. Default is row.
>
),
],
]}
/>
**Example:**
```js
registerVevComponent(MyComponent, {
name: 'My component',
props: [
{
type: 'layout',
options: {
display: 'row',
},
fields: [
{ name: 'x', type: 'number' },
{ name: 'y', type: 'number' },
{ name: 'z', type: 'number' },
],
},
],
});
```
### Upload
A field to be used for uploading files.
```ts
type: "upload",
fields: VevProps[],
```
Options:
(
<>
A comma separated list of{' '}
unique file type specifiers
.
>
),
],
]}
/>
##### Example
```js
registerVevComponent(MyComponent, {
name: 'My component',
props: [
{
type: 'upload',
accept: '.jpg,.png',
},
],
});
```
### Menu
A field for adding/editing project menus. Use in conjunction with [useMenu](/react/hooks#usemenu).
```ts
type: "menu",
```
##### Example
```js
registerVevComponent(MyComponent, {
name: 'My component',
props: [
{
name: 'projectMenu',
type: 'menu',
},
],
});
```
### Variable
A field used for getting variable keys. Use in conjunction with [useVariable](/react/hooks#usevariable) and [useSetVariable](/react/hooks#usesetvariable).
```ts
type: "variable",
```
##### Example
```js
registerVevComponent(MyComponent, {
name: 'My component',
props: [
{
name: 'variableKey',
type: 'variable',
},
],
});
```
## Types
### Context
Argument for functions initialValue
and hidden in [Vev props](/react/vev-props)
```ts
export type SchemaContextModel = {
key?: string;
value?: Partial;
storage?: SchemaStorage;
/**
* Project
*/
project?: { title: string; key: string; index: string };
/**
* Project pages
*/
pages?: { title: string; key: string; path?: string }[];
currentPage?: { title: string; key: string; path?: string; project?: string };
/**
* Named content models
*/
content?: {
key: string;
name?: string;
parentKey: string;
type: string;
content?: { [key: string]: any };
}[];
/**
* Events
*/
events?: VevContentEvent[];
};
```
## Client Rendering
For client-side rendering, we use the [Vev Embed](https://help.vev.design/en/articles/6141975-embed-your-project).
Vev Embed provides a super lightweight and fast way to render a Vev project on the client side.
#### Vev Embed
The Vev Embed is a optimized way to render a project client side. The embed will insert HTML synchronous, and then defer the hydration until HTML is fully rendered [hydrate](https://18.react.dev/reference/react-dom/client/hydrateRoot) the project. If blocking render is not desired, you can add asyn to the script tag to prevent this
* **Variable Replacement:** Supports the dynamic replacement of any Vev variable directly in the browser.
* **Performance Optimized:** Designed to be lightweight and fast, ensuring minimal impact on page load times.
### Example
This is a [Vev example project](https://editor.vev.design/edit/U1TM1EJXg9/pv5RwtffJq7), built using variables.
* **Project Overview:** This project demonstrates how variables are utilized to create dynamic content.
* **Dynamic Updates:** Variables in this project can be updated dynamically, showcasing client-side rendering in action.
```js
```
#### Codesandbox
This is a live working example.
## How to Connect to a CMS
[Vev](https://editor.vev.design/) is a powerful front-end editor designed to seamlessly integrate with any CMS (Content Management System) or other data sources, enabling dynamic content display. It supports both [client side](/cms/client-side) and [server side](/cms/server-side) rendering, making it adaptable to any web application built with any programming language or framework.

With Vev, you can build a fully customizable frontend that can be entirely self-hosted alongside your existing frontend architecture. Either to build full pages or components. It can also be used alongside existing content.
### Variables
Variables are the backbone of dynamic projects in Vev, enabling external modification of content.
[Variables](https://help.vev.design/en/articles/9674244-setting-up-and-managing-variables) are placeholders in your Vev project that dynamically update with data from a CMS or other sources. Here's how to get started:
* Identify the elements in your project (e.g., text, images, styling) that will use variables.
* Configure these elements to accept dynamic data.

**Supported variables:**
* **Text Variables:** Dynamically update text, such as titles or descriptions.
* **Image Variables:** Swap or update images without modifying the project manually.
* **List Variables:** Perfect for collections, like slider or list items.
* **Styling Variables:** Modify styles dynamically, such as colors or sizes.
### Compile project
The [Vev CLI](/cli/getting-started) allows you to [compile](/cli/commands#compile) your project into static files for seamless integration with any CMS and web application.
The compiled files also include an `embed/[pageKey].js` script, for client side rendering script, and `template/[pageKey].mustache` file for serer side rendering.
To compile the project, you run the the [compile command](/cli/commands#compile):
```bash
vev compile [project-id]
```
:::info
You can also specify the project to compile in [vev.json](/cli/commands#compile).
:::
* **Benefits of Compiling:**
* Converts Vev projects into deployable files (HTML, CSS, JavaScript).
* Facilitates efficient storage and integration with CMS data.
* **Storage Tip:** Store the compiled files in your web application's static folder for optimal performance.
### Replace variables
Vev provides two primary methods for variable replacement:
#### Client Rendering
* **How it Works:** Variables are replaced in the browser after the page loads using Vev Embed.
* **Best For:** Scenarios where you do not have a server available.
[Documentation for client rendering](/cms/client-side)
#### Server Rendering
* **How it Works:** Variables are replaced on the server using Mustache templates.
* **Best For:** Scenarios where you have server.
By following these steps and leveraging Vev's flexibility, you can create dynamic, efficient, and highly customizable front-end projects that seamlessly connect to any CMS or data source.
[Documentation for server rendering](/cms/client-side)
## Server rendering
For server rendering, Vev is using [Mustache](https://mustache.github.io/), which is a generic templating language which can be used with a wide range of programming languages like PHP, Java, .NET and JavaScript.
### Example implementations
This is a [Vev example project](https://editor.vev.design/edit/U1TM1EJXg9/pv5RwtffJq7), built using variables.
#### NodeJS
```js
import Mustache from 'mustache';
import fs from 'fs/promises';
const template = await fs.readFile('/template/p8S9MxkirVE.mustache', 'utf8');
const renderedHTML = Mustache.render(template, {
// primary
'c--hWX11RLvP': '#000 | rgba(0,0,0,0.5) | hsl(0,0%,0%) | hsla(0,0%,0%,0.5)',
// Hero image
'i-JZiYWYXa7J': {
src: 'https://img.url',
alt: 'Image alt text',
srcset: 'https://img.url 300w, https://img.url 600w, https://img.url 900w',
},
// Hero title
't-4RyfGtGYj2': 'Text',
// Hero CTA url
't-8ec2OwGaF_': 'Text',
// Hero CTA text
't-DzxChO2qSm': 'Text',
// Hero subtitle
't-hd1jsFgPV5': 'Text',
});
res.send(renderedHTML);
```
#### PHP
```php
new Mustache_Loader_FilesystemLoader('/template') // Set the template directory
]);
// Read the Mustache template file
$templateFile = 'p8S9MxkirVE.mustache';
$template = file_get_contents(__DIR__ . "/template/" . $templateFile);
// Define data for the template
$data = [
// primary
'c--hWX11RLvP' => "#000 | rgba(0,0,0,0.5) | hsl(0,0%,0%) | hsla(0,0%,0%,0.5)",
// Hero image
'i-JZiYWYXa7J' => [
"src" => "https://img.url",
"alt" => "Image alt text",
"srcset" => "https://img.url 300w, https://img.url 600w, https://img.url 900w"
],
// Hero title
't-4RyfGtGYj2' => "Text",
// Hero CTA url
't-8ec2OwGaF_' => "Text",
// Hero CTA text
't-DzxChO2qSm' => "Text",
// Hero subtitle
't-hd1jsFgPV5' => "Text",
];
// Render the template
$renderedHTML = $mustache->render($template, $data);
// Send the response
header('Content-Type: text/html');
echo $renderedHTML;
?>
```
#### Go
```go
package main
import (
"io/ioutil"
"log"
"net/http"
"github.com/hoisie/mustache"
)
func handler(w http.ResponseWriter, r *http.Request) {
// Read the Mustache template file
template, err := ioutil.ReadFile("/template/p8S9MxkirVE.mustache")
if err != nil {
log.Fatalf("Error reading template file: %v", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
// Define the data to pass into the template
data := map[string]interface{}{
"c--hWX11RLvP": "#000 | rgba(0,0,0,0.5) | hsl(0,0%,0%) | hsla(0,0%,0%,0.5)",
"i-JZiYWYXa7J": map[string]string{
"src": "https://img.url",
"alt": "Image alt text",
"srcset": "https://img.url 300w, https://img.url 600w, https://img.url 900w",
},
"t-4RyfGtGYj2": "Text",
"t-8ec2OwGaF_": "Text",
"t-DzxChO2qSm": "Text",
"t-hd1jsFgPV5": "Text",
}
// Render the template with the data
renderedHTML := mustache.Render(string(template), data)
// Write the rendered HTML to the response
w.Header().Set("Content-Type", "text/html")
_, err = w.Write([]byte(renderedHTML))
if err != nil {
log.Printf("Error writing response: %v", err)
}
}
func main() {
http.HandleFunc("/", handler)
log.Println("Server is running on port 8080...")
log.Fatal(http.ListenAndServe(":8080", nil))
}
```
## Getting started
How to get started with [Vev](https://vev.design) using the command-line interface (CLI), and local development of
custom [React components](https://reactjs.org/) in Vev.
:::tip
You need [NodeJS](https://nodejs.dev/) to run the Vev CLI.
:::
#### Install CLI
First you need to install the CLI from NPM (or Yarn).
```js
npm install -g @vev/cli
```
* [Available CLI commands](/cli/commands)
#### Authorize the CLI
After that you need a [Vev account](https://editor.vev.design), and then authorize the CLI:
```js
vev login
```
#### Bootstrap your project
It is not needed to add your components to a specific Vev project, it can be determined later if you want your component
to be available for only a specific project or for a whole account.
To create a new project:
```bash
vev create [my-project]
```
You can also use the CLI in an existing React project. Then you just need to add
the [@vev/react](https://www.npmjs.com/package/@vev/react) package.
In your project directory run:
```bash
cd [my-project]
vev init
```
### Create your first component
The `registerVevComponent` function can be used everywhere inside a `./src` directory. But all components needs to be
exported.
```ts title="my-component.js"
import React from 'react';
import { registerVevComponent } from '@vev/react';
const MyComponent = () => {
return
Hello, VeV
;
}
registerVevComponent(MyComponent, {
name: "My awesome component",
});
export default MyComponent;
```
Further reads:
* [Register component](/react/register-vev-component)
* [Vev props](/react/vev-props)
* [Vev components](/react/components)
* [Vev hooks](/react/hooks)
### Run you components
Now you are ready to run your components in the Vev Editor.
```
vev start
```
Now you can open the [Vev design editor](https://editor.vev.design/) and your components will be available in all your
projects as long as the CLI is running.
## Web workers
:::info
Web Workers allow you to run scripts in background threads, preventing heavy computations from blocking the main thread or interfering with rendering.
[Learn more about Web Workers](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API)
:::
Vev provides full support for Web Workers in CLI components.
You can import a Web Worker script directly by appending ?worker to your import path. The default export will be a worker constructor:
```tsx
import Worker from './my-worker?worker';
const myWorker = new Worker();
```
Inside the worker, you can use standard ES module imports — no need for [importScripts](https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope/importScripts).
### Example
A common use case for Web Workers in Vev is to preload and process multiple images in the background — for example, when building a video scroll component that uses images instead of an actual video file.
```ts filename="example.ts"
import { useEffect, useRef } from "react";
import ImageWorker from "./image-load-worker?worker"; // [!code focus]
export function useVideoImageWorker(images: string[]) {
const imagesRef = useRef([]);
useEffect(() => {
const worker = new ImageWorker(); // [!code focus]
worker.addEventListener('message', async (e: any) => {
const { url, index } = e.data as { index: number; url: string };
const img = await resolveImage(url);
const images = imagesRef.current;
if (images && img) images[index] = img;
});
worker.postMessage({ // [!code focus]
images, // [!code focus]
parentLocation: `${self.location.origin}${self.location.pathname}`, // [!code focus]
}); // [!code focus]
return () => worker.terminate(); // [!code focus]
}, [images]);
return imagesRef;
}
```
In this setup, the worker handles loading and caching of image sequences efficiently, allowing smooth scrubbing and playback on scroll without blocking the main UI thread.
[Full code for Video Scroll component](https://github.com/vev-design/vev-components/blob/main/video-scroll/)
## Commands
:::warning
You will first need a Vev user, [sign up here](https://editor.vev.design/signup).
:::
You also need [NodeJS](https://nodejs.dev/) **v16 or higher** installed.
To install the CLI:
```
npm i @vev/cli -g
```
### login
To authorize the CLI against the [Design Editor](https://editor.vev.design):
```bash
vev login
```
This will authorize the CLI against Vev, and save an access token to your computer which will be used for further requests. You need to login before you can start initializing packages.
To force a new login or log in to another account use:
```bash
vev login --force
```
### create
To get started easily and get a boiler of React and TypeScript setup.
```bash
vev create [my-project]
```
Based on this [boilerplate](https://github.com/vev-design/create-vev-app).
This is not required to use the CLI, you can also use your own setup and add the [@vev/react](/react/register-vev-component) package manually.
### initialize
To start working on Vev components, you need to run initialize first.
```bash
vev init
```
This will create a new component package in Vev that later can be deployed. In your local files, a new `vev.json` file will be created containing your component `key`. Never delete this file, while this will detach your component from Vev, if you do so you will have to initialize a new component package.
:::caution
Never delete the **vev.json** file.
:::
This command will also create a `.vev` folder containing all of your local builds. This folder can be deleted at any point and will be automatically recreated when needed. This folder does not need to be checked into source control.
### start
To view your components the [Design Editor](https://editor.vev.design).
```bash
vev start
```
Now you can open the [Design Editor](https://editor.vev.design) and open any project. Now you will see a green dot in the bottom of the editor. This means your local machine is connected with Vev. Clicking the dot will give you a menu where you can start using the components inside the [Design Editor](https://editor.vev.design).

To show more messages and debug messages to `stdout`, you can add the `--debug` flag to your start command.
```bash
vev start --debug
```
### deploy
```bash
vev deploy
```
This will deploy the component and make it available in the [Design Editor](https://editor.vev.design).
Available options can be set in [vev.json](/cli/configuration).
This will create *one* optimized JavaScript and CSS bundle which will be shared among all widgets within the package.
For optimization, make separate packages if your widgets does not shared common code, using *Context* (or other global shared logic). This will create more optimal bundles.
### compile
```bash
vev compile [project-id]
```
This will compile the project into a production ready bundle of HTML, CSS and JavaScript that can be served on any webserver.
This can also be used to load [CMS data into the Vev project](/cms).
##### Options:
* `--dist`: The output folder for the generated files (default is: **vev/** )
```bash
vev compile [project-id] --dist your-destination-folder/
```
This can also be configured in the `vev.json` file:
```json
{
"library": {
"key": "project-key",
"dist": "/your-dist-folder"
}
}
```
##### Output
This will create and safe the following files in the destination folder:
```bash
├── index.html # HTML for the index page
├── embed.html # HTML file with embed
├── embed.js # Embed script
├── assets # All assets for the project, including images etc.
│ ├── shared.css # Shared CSS for all pages
│ ├── vev.css # Default Vev CSS
│ ├── vev.js # The Vev.js script
│ ├── interactions.js # Interactions scripts
│ └── ... # All assets
├── page-2 # Additional pages
├── index.html # HTML for page
├── embed.html # HTML embed for page
├── embed.js # Embed script
```
import Table from '../../components/Table';
## Configuration
To configure your component package use the `vev.json` file. This file will contain the `key` for the component which connects your component package to Vev.
:::danger
Never delete the **vev.json** file.
:::
### Options
(
<>
They key is auto generated when running vev init and should
never been changed.
>
),
],
[
{
name: 'shareWithAccount',
type: 'boolean',
},
'Option to make the component available in the add menu for everyone on your account after deployed.',
],
[
{
name: 'library',
type: 'object',
},
() => (
<>
Object containing key, which is the Vev project key, and dist{' '}
(optional) to specify the destination folder for the compiled files.
>
),
],
]}
/>
## Tailwind
The Vev CLI natively supports [Tailwind CSS](https://tailwindcss.com/docs/installation), requiring minimal setup. Simply add a Tailwind configuration file to the root of your project.
No extra configuration is necessary, as the `tailwindcss` package is already included.
```js title="tailwind.config.js"
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ['src/**/*.{ts,tsx}'],
corePlugins: {
preflight: false,
},
};
```
:::info
[Tailwind](https://tailwindcss.com/docs/installation) is a utility-first framework that allows you to design directly in your HTML using predefined classes, without needing to write custom CSS. It’s ideal for building responsive, custom designs quickly.
:::
### Disabling preflight
We recommend disabling [Tailwind's Preflight](https://tailwindcss.com/docs/preflight#disabling-preflight), as it can conflict with Vev’s built-in reset styles, potentially causing issues with how styles are applied.
You can customize the configuration, add plugins, and include additional packages as you normally would with Tailwind.
## Using Secrets
### Plugin form
If you need to use credentials or other secrets like an API-key to connect to your asset repository use the `form` part
of `registerVevComponent`:
```typescript
registerVevPlugin({
form: [
{
type: "string",
name: "API_KEY",
title: "API Key",
},
],
...
});
```
The type of the form is described [here](/react/vev-props)
This form will show up when connecting the asset source and will deploy together with the serverless function, meaning it's not retrievable by anyone:

### Retrieving secrets
The values will then be available on the `env` object in the handler function:
```typescript
async function handler(
env: Record,
...
): Promise {
const apiKeyFromForm = env.API_KEY;
...
```
## Asset Source
An asset source is handled by the plugins system. You can add a plugin to extend Vev assets library and connect a DAM or other asset sources.
### Create a new plugin
To create an asset source plugin first clone [this](https://github.com/vev-design/editor-plugins/tree/main/asset-source-template) template project.
Change the name to something appropriate for your plugin and assign a unique ID (this can be anything, but it must be unique to your account):
```javascript
registerVevPlugin({
name: "Your plugin name",
id: '[your-id]',
...
});
```
In your project directory run:
```bash
cd [my-project]
vev init
```
and after this is successful:
```bash
vev deploy
```
### Connecting a plugin
To connect and start using your plugin go to [editor plugins](https://editor.vev.design/account/editor-plugin#) in account settings.
You should now see your new plugin in the list and be able to connect it:

After connecting the plugin you should now be able to see the results in the Vev design editor:

### Logs and debugging
To see the logs of your asset source after you have connected it go to [editor plugins](https://editor.vev.design/account/editor-plugin#) in account settings.
In the dropdown to the right there is an option to open the log viewer:

### Further reading
* To learn more about the handler function and supported return types, visit [this page](/asset-source/handler-function).
* For information on handling secrets, such as API keys and other credentials, go [here](/asset-source/api-keys).
* To understand how to add configurable settings to your plugin, refer to \[this guide].
* For details on adding filtering or advanced search features in the Vev editor, check out \[this resource].
## Handler function
The handler function retrieves information about your assets and return them in a format the Vev editor understands.
The handler function returns an array of image or video assets.
```typescript
registerVevPlugin({
handler: () => {...},
...
});
```
### Asset types
#### Base asset
All asset types extends the base asset type:
```typescript
export interface BaseProjectAsset {
key: string; // A unique id for the asset
mimeType: string; // The mime type of the asset (image/jpeg, video/mp4 etc.)
url: string; // The url where the Vev editor can reach the asset
filename?: string; // Optional filename
selfHosted?: boolean; // If you want to host your own asset make sure this is true
}
```
#### Image assets
```typescript
export interface ProjectImageAsset extends BaseProjectAsset {
dimension?: { width: number; height: number };
thumb?: string; // A thumbnail for faster loading in the Vev editor
imageSizes?: ImageSizes; // A set of custom image sizes if you are hosting your own assets
}
```
#### Video assets
```typescript
export interface ProjectVideoAsset extends BaseProjectAsset {
videoSample?: string; // A smaller video sample for faster loading in the Vev editor
videoThumbnail?: string; // A thumbnail for faster loading in the Vev editor
dimension?: { width: number; height: number };
duration?: number; // The duration of the video on seconds
additionalSources: Omit[]; // Additional sources/formats if you are hosting your own assets
}
```
## Custom Asset Sources
Custom asset sources enable you to integrate videos, images, and other media assets from your CMS, [Digital Asset Management (DAM)](https://en.wikipedia.org/wiki/Digital_asset_management)
systems, or other media repositories into the Vev editor.
The asset sources are deployed in their own isolated environment, meaning any secrets or credentials are
not readable by anyone.
To create a custom asset source, you need to install the Vev [CLI](/cli/getting-started).
For additional information, begin with our [Getting Started guide](getting-started) or explore our [repository](https://github.com/vev-design/editor-plugins) for more advanced example implementations.
:::info
User-made asset sources are in beta. Contact support to get access to deploying your custom asset sources.
:::
:::info
Asset source functions do not run in a Node.js environment, so any libraries with dependencies specific to Node.js will not work.
:::
## Authorization
To authorize, you need to create a token.
### Create API key
In the [Design Editor](https://editor.vev.design), go to [user profile](https://editor.vev.design/user) and create a API key, with the relevant scope.

The API key will be valid forever until it is revoked.
### Authorized request
Test your newly created token:
```bash
curl --header "x-vev-key: [your-key]" https://api.vev.design/api-key/introspection
```
To make authorized requests, you need to add the `x-vev-key` header to every request.
import Redoc from '../../components/Redoc';