diff --git a/assets/js/base/components/cart-checkout/local-pickup-select/index.tsx b/assets/js/base/components/cart-checkout/local-pickup-select/index.tsx
new file mode 100644
index 00000000000..3a4d42ba6cc
--- /dev/null
+++ b/assets/js/base/components/cart-checkout/local-pickup-select/index.tsx
@@ -0,0 +1,57 @@
+/**
+ * External dependencies
+ */
+import { RadioControlOption } from '@woocommerce/base-components/radio-control/types';
+import { CartShippingPackageShippingRate } from '@woocommerce/types';
+/**
+ * Internal dependencies
+ */
+import RadioControl from '../../radio-control';
+
+interface LocalPickupSelectProps {
+ title?: string | undefined;
+ setSelectedOption: ( value: string ) => void;
+ selectedOption: string;
+ pickupLocations: CartShippingPackageShippingRate[];
+ onSelectRate: ( value: string ) => void;
+ renderPickupLocation: (
+ location: CartShippingPackageShippingRate,
+ pickupLocationsCount: number
+ ) => RadioControlOption;
+ packageCount: number;
+}
+/**
+ * Local pickup select component, used to render a package title and local pickup options.
+ */
+export const LocalPickupSelect = ( {
+ title,
+ setSelectedOption,
+ selectedOption,
+ pickupLocations,
+ onSelectRate,
+ renderPickupLocation,
+ packageCount,
+}: LocalPickupSelectProps ) => {
+ // Hacky way to check if there are multiple packages, this way is borrowed from `assets/js/base/components/cart-checkout/shipping-rates-control-package/index.tsx`
+ // We have no built-in way of checking if other extensions have added packages.
+ const multiplePackages =
+ document.querySelectorAll(
+ '.wc-block-components-local-pickup-select .wc-block-components-radio-control'
+ ).length > 1;
+ return (
+
+ { multiplePackages && title ?
{ title }
: false }
+
{
+ setSelectedOption( value );
+ onSelectRate( value );
+ } }
+ selected={ selectedOption }
+ options={ pickupLocations.map( ( location ) =>
+ renderPickupLocation( location, packageCount )
+ ) }
+ />
+
+ );
+};
+export default LocalPickupSelect;
diff --git a/assets/js/base/components/cart-checkout/local-pickup-select/test/index.tsx b/assets/js/base/components/cart-checkout/local-pickup-select/test/index.tsx
new file mode 100644
index 00000000000..6d3bd6da04a
--- /dev/null
+++ b/assets/js/base/components/cart-checkout/local-pickup-select/test/index.tsx
@@ -0,0 +1,121 @@
+/**
+ * External dependencies
+ */
+import { render, screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+/**
+ * Internal dependencies
+ */
+import LocalPickupSelect from '..';
+
+describe( 'LocalPickupSelect', () => {
+ const TestComponent = ( {
+ selectedOptionOverride = null,
+ onSelectRateOverride = null,
+ }: {
+ selectedOptionOverride?: null | ( ( value: string ) => void );
+ onSelectRateOverride?: null | ( ( value: string ) => void );
+ } ) => (
+ {
+ return {
+ value: `${ location.rate_id }`,
+ onChange: jest.fn(),
+ label: `${ location.name }`,
+ description: `${ location.description }`,
+ };
+ } }
+ />
+ );
+ it( 'Does not render the title if only one package is present on the page', () => {
+ render( );
+ expect( screen.queryByText( 'Package 1' ) ).not.toBeInTheDocument();
+ } );
+ it( 'Does render the title if more than one package is present on the page', () => {
+ const { rerender } = render(
+
+ );
+ // Render twice so our component can check the DOM correctly.
+ rerender(
+ <>
+
+
+ >
+ );
+ rerender(
+ <>
+
+
+ >
+ );
+
+ expect( screen.getByText( 'Package 1' ) ).toBeInTheDocument();
+ } );
+ it( 'Calls the correct functions when changing selected option', () => {
+ const setSelectedOption = jest.fn();
+ const onSelectRate = jest.fn();
+ render(
+
+ );
+ userEvent.click( screen.getByText( 'Store 2' ) );
+ expect( setSelectedOption ).toHaveBeenLastCalledWith( '2' );
+ expect( onSelectRate ).toHaveBeenLastCalledWith( '2' );
+ userEvent.click( screen.getByText( 'Store 1' ) );
+ expect( setSelectedOption ).toHaveBeenLastCalledWith( '1' );
+ expect( onSelectRate ).toHaveBeenLastCalledWith( '1' );
+ } );
+} );