Subscribe CTA
A subscribe button that presses itself, with a bell that rings once after the click.
SubscribeCta, defaults
Installation
npx shadcn add https://www.reelcn.dev/r/subscribe-cta.jsonFiles land in src/reelcn/ with the items it builds on. Dependencies: remotion.
Usage
<Sequence from={120} durationInFrames={90}>
<SubscribeCta label="Subscribe" note="New videos every Thursday" />
</Sequence>Props
PropTypeDescription
delaynumberFrames to wait before entering.
durationnumberEnter duration in frames. Defaults to 0.6s. Some items reuse
duration for an unrelated local timing. See that item's own prop docs.exitnumber | booleanExit at the end of the parent `<Sequence>`.
false keeps it on screen, a number sets the exit length in frames.motionMotionPersonalityOverride the theme's motion personality.
posterbooleanRenders fully entered, with no exit: for a still frame or thumbnail. Defaults to
false.holdFramesnumberDelays the start of the exit window by this many frames, for a minimum on-screen hold. Defaults to
0.labelstring Default
"Subscribe".notestringLine under the button.
pressAtnumberFrame, relative to this component, when the button presses itself.
colorstring
backgroundstring
styleCSSProperties
classNamestring
Built from
Use
- The last five seconds of a video, or right after a payoff
- Any moment that asks for one specific action
Avoid
- Several competing asks at once, show one call to action
Guides
Components take their colors from a theme, their timing from motion props and their layout from the canvas.
Source
registry/items/subscribe-cta.tsx
/**
* @title Subscribe CTA
* @category social
* @description A subscribe button that presses itself, with a bell that rings once after the click.
* @duration 45
* @use The last five seconds of a video, or right after a payoff
* @use Any moment that asks for one specific action
* @avoid Several competing asks at once, show one call to action
* @tags cta, subscribe, bell, youtube
* @example
* <Sequence from={120} durationInFrames={90}>
* <SubscribeCta label="Subscribe" note="New videos every Thursday" />
* </Sequence>
*/
import type React from "react";
import { AbsoluteFill } from "remotion";
import { alpha, type MotionProps, tween, useMotion, useTheme, useViewport } from "./core";
export type SubscribeCtaProps = MotionProps & {
label?: string;
/** Line under the button. */
note?: string;
/** Frame, relative to this component, when the button presses itself. */
pressAt?: number;
color?: string;
background?: string;
style?: React.CSSProperties;
className?: string;
};
export function SubscribeCta({
label = "Subscribe",
note,
pressAt,
color,
background,
style,
className,
...motion
}: SubscribeCtaProps) {
const theme = useTheme();
const { u, safe } = useViewport();
const m = useMotion(motion);
const press = m.delay + (pressAt ?? Math.round(m.fps * 0.9));
const down = tween(m.frame, m.fps, { from: press, duration: Math.round(m.fps * 0.12), motion: "snappy" });
const up = tween(m.frame, m.fps, {
from: press + Math.round(m.fps * 0.12),
duration: Math.round(m.fps * 0.2),
motion: "bouncy",
});
const ring = tween(m.frame, m.fps, {
from: press + Math.round(m.fps * 0.2),
duration: Math.round(m.fps * 0.5),
motion: "linear",
});
// One ring: the bell rocks twice and settles.
const tilt = ring > 0 && ring < 1 ? Math.sin(ring * Math.PI * 4) * (1 - ring) * 16 : 0;
const enter = Math.min(m.enter, 1);
return (
<AbsoluteFill
className={className}
style={{ alignItems: "center", justifyContent: "flex-end", padding: `0 ${safe.x}px ${safe.bottom}px`, ...style }}
>
<div
style={{
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: u(12),
opacity: enter * (1 - m.exit),
translate: `0 ${(1 - enter) * u(20)}px`,
}}
>
<div
style={{
display: "flex",
alignItems: "center",
gap: u(14),
padding: `${u(16)}px ${u(30)}px`,
borderRadius: u(theme.radius),
background: background ?? theme.colors.accent,
color: color ?? theme.colors.accentForeground,
fontFamily: theme.fonts.heading,
fontWeight: theme.headingWeight,
fontSize: u(36),
boxShadow: `0 ${u(14)}px ${u(40)}px ${alpha("#000000", 0.28)}`,
scale: String(1 - down * 0.06 + up * 0.06),
}}
>
<svg
width={u(34)}
height={u(34)}
viewBox="0 0 24 24"
fill="none"
aria-hidden="true"
style={{ rotate: `${tilt}deg` }}
>
<path
d="M6 9a6 6 0 1 1 12 0c0 4 1.2 5.4 2 6H4c.8-.6 2-2 2-6Z"
stroke="currentColor"
strokeWidth={2}
strokeLinejoin="round"
/>
<path d="M10 19a2 2 0 0 0 4 0" stroke="currentColor" strokeWidth={2} strokeLinecap="round" />
</svg>
<span>{label}</span>
</div>
{note ? <div style={{ fontSize: u(24), color: theme.colors.muted }}>{note}</div> : null}
</div>
</AbsoluteFill>
);
}