Skip to content

Jetpack Logo: support light/dark variants, clean up markup #103279

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 9 commits into
base: trunk
Choose a base branch
from
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { JetpackLogo } from '@automattic/components';
import { useTranslate } from 'i18n-calypso';
import Banner from 'calypso/components/banner';
import { useSelector } from 'calypso/state';
Expand All @@ -23,7 +22,6 @@ export default function A4APluginsJetpackBanner() {
description={ translate(
'To manage plugins, Jetpack must be activated on each site. Your Pressable plan includes Jetpack Complete for free. Activate it to access plugin management in this dashboard.'
) }
icon={ <JetpackLogo size={ 16 } /> }
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The jetpack logo here was ignored in the underlying Banner component

className="plugins__jetpack-banner"
dismissPreferenceName={ JETPACK_BANNER_DISMISS_PREFERENCE }
disableCircle
Expand Down
3 changes: 2 additions & 1 deletion client/blocks/import/style/base.scss
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,8 @@
position: absolute;
top: 1px;
left: 0;
fill: var(--studio-gray-20);
color: var(--studio-gray-20);
fill: currentColor;
Comment on lines +185 to +186
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switching to color + fill for a more comprehensive way to change the logo's color that is backwards compatible, but also works with the new version of the jetpack logo

}
}

Expand Down
2 changes: 1 addition & 1 deletion client/components/domains/domain-suggestion/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,7 @@
body.is-section-stepper,
body.is-section-signup {
&:not(:has(.step-container-v2)) {
svg:not(.jetpack-logo, .social-buttons__button svg) {
svg:not(.step-container__jetpack-powered-logo, .social-buttons__button svg) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using .step-container__jetpack-powered-logo instead of the internal .jetpack-logo class

padding-top: 0;
margin-right: 0;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ export const MigrationPlanFeatureList = ( {
)
) }
<li className="import__upgrade-plan-feature logo">
<JetpackLogo size={ 24 } />
<JetpackLogo monochrome size={ 24 } />
</li>
{ migrationPlanFeatures[ 'jetpackFeatures' ].map( ( feature: ReactNode, index: number ) => (
<li key={ index } className="import__upgrade-plan-feature">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,9 @@ export const QuickLinks = ( {
<>
<ActionBox
hideLinkIndicator
gridicon="plans"
iconComponent={
<JetpackLogo monochrome className="quick-links__action-box-icon" />
}
Comment on lines +238 to +240
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using the same Jetpack logo as other jetpack-related quick links, instead of a slightly different jetpack logo

label={ translate( 'Create a logo with Jetpack AI' ) }
onClick={ () => setIsAILogoGeneratorOpen( true ) }
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@
.quick-links__action-box-icon {
width: 24px;
height: 24px;
fill: var(--color-neutral-60);
fill: currentColor;
color: var(--color-neutral-60);
Comment on lines +74 to +75
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as before — ie. apply the same fill color to both existing SVGs and also the new Jetpack color

transition: all 0.1s;

&.dashicons {
Expand Down Expand Up @@ -137,10 +138,3 @@
.quick-links__action-box-label {
font-size: $font-body-small;
}

.quick-links__boxes .jetpack-logo {
width: 16px;
height: 16px;
margin-left: 2px;
}

139 changes: 85 additions & 54 deletions packages/components/src/logos/jetpack-logo/index.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import colorStudio from '@automattic/color-studio';
import clsx from 'clsx';

import { useId } from 'react';
/**
* Module constants
*/
const PALETTE = colorStudio.colors;
const COLOR_JETPACK = PALETTE[ 'Jetpack Green 40' ];
const COLOR_WHITE = PALETTE[ 'White' ]; // eslint-disable-line dot-notation

type JetpackLogoProps = {
/**
Expand All @@ -24,79 +23,111 @@ type JetpackLogoProps = {
* @default false
*/
monochrome?: boolean;
/**
* Theme of the logo.
* - `default`: use the default theme (ie. inherit).
* - `light`: render.
* - `dark`: use the dark theme.
* @default 'default'
*/
Comment on lines +26 to +32
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I fully understand why we need a theme prop instead of just using currentColor for the "foreground" parts (with the "cutout" being transparent in the monochrome case). Is that part of what we're figuring out with the brand designers right now?

Copy link
Contributor Author

@ciampo ciampo May 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The monochrome version having a cutout vs the brand color version being filled white is part of the logo specs (see TTzDk3dzct551hYWRXPFa3-fi-2395_14811 ):

  • branded color, light theme: white triangles, green circle, black text
  • branded color, dark theme: white triangles, green circle, white text
  • monochrome, light or dark: cutout mask, currentColor circle, currentColor text

Screenshot 2025-05-09 at 14 14 23

This is a requirement regardless of the "using hardcoded white/black" question.

In other words, I added the "theme" prop to generate all possible combinations from the specs. Especially today, we need a way to render the logo accordingly on a light/dark background following those specs. And I believe it would apply to most (if not all) logos.

When we add a Theme component, we may soft-deprecate the prop and recommend wrapping the logos in a Theme component instead.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't use currentColor text in the branded color version?

Copy link
Contributor Author

@ciampo ciampo May 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're not sure if we can ditch the hardcoded white/black for the "Jetpack" text part in favour of always using currentColor, that's something to discuss with brand designers (@jameskoster is on it, although maybe a direct ping to @keoshi could be quicker?).

If it were possible to always use currentColor for the "Jetpack" text part, we could do without a theme prop, but we'd need to hardcode the triangles to #fff — which potentially could go out of sync with the color of the Jetpack text

theme?: 'default' | 'light' | 'dark';
};

const LogoMarkTriangle = ( { fill }: Pick< React.SVGProps< SVGPathElement >, 'fill' > ) => {
return (
<path
fill={ fill }
fillRule="evenodd"
d="M47.9 9.92V58.3H23L47.9 9.92Zm5.06 80.16V41.6h25l-25 48.48Z"
clipRule="evenodd"
/>
);
};

const LogoPathSize32 = ( { monochrome = false }: Pick< JetpackLogoProps, 'monochrome' > ) => {
const primary = monochrome ? 'white' : COLOR_JETPACK;
const secondary = monochrome ? 'black' : COLOR_WHITE;
const LogoMarkCircle = ( {
fill,
mask,
}: Pick< React.SVGProps< SVGPathElement >, 'fill' | 'mask' > ) => {
return <circle cx="50" cy="50" r="50" fill={ fill } mask={ mask } />;
};

const LogoMark = ( { monochrome = false }: Pick< JetpackLogoProps, 'monochrome' > ) => {
return (
<>
<path
className="jetpack-logo__icon-circle"
fill={ primary }
d="M16,0C7.2,0,0,7.2,0,16s7.2,16,16,16s16-7.2,16-16S24.8,0,16,0z"
/>
<polygon
className="jetpack-logo__icon-triangle"
fill={ secondary }
points="15,19 7,19 15,3 "
/>
<polygon
className="jetpack-logo__icon-triangle"
fill={ secondary }
points="17,29 17,13 25,13 "
{ /* In the monochrome version: the triangles are masked out. In the
color-branded version, the triangles are hardcoded white. */ }
<LogoMarkCircle
fill={ monochrome ? 'currentColor' : COLOR_JETPACK }
mask={ monochrome ? 'url(#cutout)' : undefined }
/>
{ monochrome ? (
<defs>
<mask id="cutout">
{ /* Full white rectangle to start with full visibility */ }
<rect width="100" height="100" fill="#fff" />
{ /* The shape we want to "cut out" is black in the mask */ }
<LogoMarkTriangle fill="#000" />
</mask>
</defs>
) : (
<LogoMarkTriangle fill="#fff" />
) }
</>
);
};

const LogoPathSize32Monochrome = () => (
<>
<mask id="jetpack-logo-mask">
<LogoPathSize32 monochrome />
</mask>
const LogoText = ( {
monochrome = false,
theme = 'default',
}: Pick< JetpackLogoProps, 'monochrome' | 'theme' > ) => {
let textFillColor;
// TODO: check with brand designers if we have to hardcode to white/black,
// or if we can use the default text color from the theme.
if ( monochrome ) {
textFillColor = 'currentColor';
} else if ( theme === 'dark' ) {
textFillColor = '#fff';
} else {
// NOTE: currently, `default` theme behaves like `light`. But in the future,
// when the `Theme` package is ready, `default` will inherit whatever color
// scheme is set at the `Theme` level.
textFillColor = '#000';
}
return (
<path
className="jetpack-logo__icon-monochrome"
d="M16,0C7.2,0,0,7.2,0,16s7.2,16,16,16s16-7.2,16-16S24.8,0,16,0z"
mask="url( #jetpack-logo-mask )"
fill={ textFillColor }
d="M129.02 83.02c-1.43-2.2-2.76-4.4-4.1-6.5 7.06-4.29 9.45-7.72 9.45-14.21V24.8h-8.3v-7.16h17.64V60.4c0 10.88-3.15 16.99-14.69 22.62ZM202.93 57.44c0 3.63 2.57 4.01 4.3 4.01 1.7 0 4.19-.57 6.1-1.14v6.67a28.36 28.36 0 0 1-9.26 1.53c-4.57 0-9.91-1.72-9.91-9.73V39.12h-4.87v-6.77h4.87V22.33h8.77v10.02h11.06v6.77h-11.06v18.32ZM221.24 86.35v-54.1h8.4v3.25c3.33-2.58 7.05-4.2 11.63-4.2 7.91 0 14.2 5.53 14.2 17.46 0 11.83-6.86 19.66-18.21 19.66-2.76 0-4.96-.39-7.25-.86v18.7h-8.77v.1Zm17.74-47.8c-2.58 0-5.82 1.24-8.87 3.91v18.42c1.9.38 3.9.67 6.58.67 6.2 0 9.72-3.92 9.72-12.12 0-7.54-2.57-10.88-7.43-10.88ZM290 67.65h-8.2v-3.91h-.2c-2.86 2.2-6.39 4.58-11.63 4.58-4.58 0-9.54-3.34-9.54-10.11 0-9.07 7.73-10.79 13.16-11.55l7.73-1.05v-1.05c0-4.77-1.91-6.3-6.4-6.3-2.19 0-7.34.67-11.53 2.39l-.76-7.06a44.13 44.13 0 0 1 13.44-2.3c8.58 0 14.12 3.44 14.12 13.65v22.71h-.2Zm-8.78-16.5-7.25 1.14c-2.19.29-4.48 1.62-4.48 4.87 0 2.86 1.62 4.48 4 4.48 2.58 0 5.35-1.53 7.73-3.24v-7.26ZM326.23 66.5c-3.62 1.25-6.86 2.01-10.97 2.01-13.15 0-18.4-7.54-18.4-18.51 0-11.55 7.25-18.7 18.98-18.7 4.38 0 7.05.76 10.01 1.72v7.44c-2.57-.96-6.3-2-9.92-2-5.34 0-9.92 2.86-9.92 11.06 0 9.07 4.58 11.83 10.4 11.83 2.76 0 5.82-.57 9.92-2.19v7.35h-.1ZM342.82 47.52c.77-.86 1.34-1.72 12.4-15.17h11.44l-14.3 16.8L368 67.74h-11.44l-13.64-16.8v16.8h-8.77v-50.1h8.77v29.87h-.1ZM182.71 66.5c-4.57 1.44-8.48 2.01-13.06 2.01-11.25 0-18.22-5.63-18.22-18.8 0-9.63 5.92-18.41 17.26-18.41 11.26 0 15.17 7.82 15.17 15.26 0 2.49-.2 3.82-.29 5.25h-22.7c.2 7.73 4.58 9.54 11.16 9.54 3.63 0 6.87-.85 10.59-2.19v7.35h.1Zm-8-20.5c0-4.3-1.44-8.02-6.11-8.02-4.39 0-7.06 3.15-7.63 8.01h13.73Z"
/>
</>
);
);
};

// Derived from the SVG logo
const LOGO_HEIGHT = 100;
const LOGO_WIDTH = 100;
const LOGO_WIDTH_FULL = 368;

export const JetpackLogo = ( {
full = false,
monochrome = false,
theme = 'default',
size = 32,
className,
...props
}: JetpackLogoProps & React.SVGProps< SVGSVGElement > ) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As we normalize the other logo components, it might be good to define a shared TS interface (e.g. A8CLogoProps).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good, alghough I'd say a separate follow-up task

const classes = clsx( 'jetpack-logo', className );

if ( full === true ) {
return (
<svg height={ size } className={ classes } viewBox="0 0 118 32" { ...props }>
<title>Jetpack</title>
{ monochrome ? <LogoPathSize32Monochrome /> : <LogoPathSize32 /> }
<path
className="jetpack-logo__text"
d="M41.3 26.6c-.5-.7-.9-1.4-1.3-2.1 2.3-1.4 3-2.5 3-4.6V8h-3V6h6v13.4C46 22.8 45 24.8 41.3 26.6zM58.5 21.3c-1.5.5-2.7.6-4.2.6-3.6 0-5.8-1.8-5.8-6 0-3.1 1.9-5.9 5.5-5.9s4.9 2.5 4.9 4.9c0 .8 0 1.5-.1 2h-7.3c.1 2.5 1.5 2.8 3.6 2.8 1.1 0 2.2-.3 3.4-.7C58.5 19 58.5 21.3 58.5 21.3zM56 15c0-1.4-.5-2.9-2-2.9-1.4 0-2.3 1.3-2.4 2.9C51.6 15 56 15 56 15zM65 18.4c0 1.1.8 1.3 1.4 1.3.5 0 2-.2 2.6-.4v2.1c-.9.3-2.5.5-3.7.5-1.5 0-3.2-.5-3.2-3.1V12H60v-2h2.1V7.1H65V10h4v2h-4V18.4zM71 10h3v1.3c1.1-.8 1.9-1.3 3.3-1.3 2.5 0 4.5 1.8 4.5 5.6s-2.2 6.3-5.8 6.3c-.9 0-1.3-.1-2-.3V28h-3V10zM76.5 12.3c-.8 0-1.6.4-2.5 1.2v5.9c.6.1.9.2 1.8.2 2 0 3.2-1.3 3.2-3.9C79 13.4 78.1 12.3 76.5 12.3zM93 22h-3v-1.5c-.9.7-1.9 1.5-3.5 1.5-1.5 0-3.1-1.1-3.1-3.2 0-2.9 2.5-3.4 4.2-3.7l2.4-.3v-.3c0-1.5-.5-2.3-2-2.3-.7 0-2.3.5-3.7 1.1L84 11c1.2-.4 3-1 4.4-1 2.7 0 4.6 1.4 4.6 4.7L93 22zM90 16.4l-2.2.4c-.7.1-1.4.5-1.4 1.6 0 .9.5 1.4 1.3 1.4s1.5-.5 2.3-1V16.4zM104.5 21.3c-1.1.4-2.2.6-3.5.6-4.2 0-5.9-2.4-5.9-5.9 0-3.7 2.3-6 6.1-6 1.4 0 2.3.2 3.2.5V13c-.8-.3-2-.6-3.2-.6-1.7 0-3.2.9-3.2 3.6 0 2.9 1.5 3.8 3.3 3.8.9 0 1.9-.2 3.2-.7V21.3zM110 15.2c.2-.3.2-.8 3.8-5.2h3.7l-4.6 5.7 5 6.3h-3.7l-4.2-5.8V22h-3V6h3V15.2z"
/>
</svg>
);
}

if ( 24 === size ) {
return (
<svg className={ classes } height="24" width="24" viewBox="0 0 24 24" { ...props }>
<path d="M12,2C6.5,2,2,6.5,2,12s4.5,10,10,10s10-4.5,10-10S17.5,2,12,2z M11,14H6l5-10V14z M13,20V10h5L13,20z" />
</svg>
);
}

const titleId = useId();
return (
<svg className={ classes } height={ size } width={ size } viewBox="0 0 32 32" { ...props }>
{ monochrome ? <LogoPathSize32Monochrome /> : <LogoPathSize32 /> }
<svg
// TODO: prefix classname with a8c-components-* (or remove it entirely with CSS modules)
className={ clsx( 'jetpack-logo', className ) }
// Set the height, the width will be automatically set according to the viewBox
height={ size }
aria-labelledby={ titleId }
{ ...props }
viewBox={ `0 0 ${ full ? LOGO_WIDTH_FULL : LOGO_WIDTH } ${ LOGO_HEIGHT }` }
>
<title id={ titleId }>Jetpack</title>
<LogoMark monochrome={ monochrome } />
{ full ? <LogoText monochrome={ monochrome } theme={ theme } /> : null }
</svg>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,23 @@ import type { Meta, StoryObj } from '@storybook/react';
const meta: Meta< typeof JetpackLogo > = {
title: 'Unaudited/Logos/JetpackLogo',
component: JetpackLogo,
decorators: [
( Story, { args } ) => (
<div
style={ {
backgroundColor: args.theme === 'dark' ? '#000' : '#fff',
color: args.theme === 'dark' ? '#fff' : '#000',
minHeight: '100px',
padding: '1rem',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
} }
>
<Story />
</div>
),
],
};
export default meta;

Expand All @@ -17,6 +34,10 @@ export const Full: Story = {
},
};

/**
* The monochrome version uses the same color as the inherited text color,
* and uses a mask for the triangles in the logo (instead of a fill).
*/
export const Monochrome: Story = {
args: {
monochrome: true,
Expand Down
10 changes: 8 additions & 2 deletions packages/onboarding/src/step-container/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,12 @@ const StepContainer: React.FC< Props > = ( {
{ headerButton && <div className="step-container__header-button">{ headerButton }</div> }
{ showHeaderJetpackPowered && (
<div className="step-container__header-jetpack-powered">
<JetpackLogo monochrome size={ 18 } /> <span>{ translate( 'Jetpack powered' ) }</span>
<JetpackLogo
className="step-container__jetpack-powered-logo"
monochrome
size={ 18 }
/>{ ' ' }
<span>{ translate( 'Jetpack powered' ) }</span>
</div>
) }
</div>
Expand All @@ -214,7 +219,8 @@ const StepContainer: React.FC< Props > = ( {
) }
{ showJetpackPowered && (
<div className="step-container__jetpack-powered">
<JetpackLogo monochrome size={ 18 } /> <span>{ translate( 'Jetpack powered' ) }</span>
<JetpackLogo className="step-container__jetpack-powered-logo" monochrome size={ 18 } />{ ' ' }
<span>{ translate( 'Jetpack powered' ) }</span>
</div>
) }

Expand Down
2 changes: 1 addition & 1 deletion packages/onboarding/src/step-container/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@
font-weight: 400;
line-height: 20px;

svg {
.step-container__jetpack-powered-logo {
margin-inline-end: 6px;
}
}
Expand Down