diff --git a/apps/playground-web/public/headless-ui-header.png b/apps/playground-web/public/headless-ui-header.png
new file mode 100644
index 00000000000..cf87071feea
Binary files /dev/null and b/apps/playground-web/public/headless-ui-header.png differ
diff --git a/apps/playground-web/src/app/connect/ui/nft/page.tsx b/apps/playground-web/src/app/connect/ui/nft/page.tsx
new file mode 100644
index 00000000000..44d016b8ed5
--- /dev/null
+++ b/apps/playground-web/src/app/connect/ui/nft/page.tsx
@@ -0,0 +1,56 @@
+import { APIHeader } from "@/components/blocks/APIHeader";
+import {
+ NftCardDemo,
+ NftDescriptionBasic,
+ NftMediaBasic,
+ NftMediaOverride,
+ NftNameBasic,
+} from "@/components/headless-ui/nft-examples";
+import ThirdwebProvider from "@/components/thirdweb-provider";
+import { metadataBase } from "@/lib/constants";
+import type { Metadata } from "next";
+
+export const metadata: Metadata = {
+ metadataBase,
+ title: "NFT Components",
+ description:
+ "Elevate your NFT marketplace with our React headless UI components, engineered for seamless digital asset transactions. These customizable, zero-styling components simplify NFT interactions while giving developers complete freedom to craft their perfect user interface.",
+};
+
+export default function Page() {
+ return (
+
+
+
+ Elevate your NFT applications with our React headless UI
+ components, engineered for seamless digital asset transactions.
+ These customizable, zero-styling components simplify NFT
+ interactions while giving developers complete freedom to craft
+ their perfect user interface.
+ >
+ }
+ docsLink="https://portal.thirdweb.com/react/v5/connecting-wallets/ui-components"
+ heroLink="/headless-ui-header.png"
+ />
+
+
+
+
+
+
+
+ );
+}
diff --git a/apps/playground-web/src/app/connect/ui/page.tsx b/apps/playground-web/src/app/connect/ui/page.tsx
new file mode 100644
index 00000000000..48aaccb8075
--- /dev/null
+++ b/apps/playground-web/src/app/connect/ui/page.tsx
@@ -0,0 +1,80 @@
+import { APIHeader } from "@/components/blocks/APIHeader";
+import {
+ AccountAddressBasic,
+ AccountAddressFormat,
+ AccountAvatarBasic,
+ AccountBalanceBasic,
+ AccountBalanceCustomToken,
+ AccountBalanceFormat,
+ AccountBalanceUSD,
+ AccountBlobbieBasic,
+ AccountNameBasic,
+ AccountNameCustom,
+ ConnectDetailsButtonClone,
+} from "@/components/headless-ui/account-examples";
+import ThirdwebProvider from "@/components/thirdweb-provider";
+import { metadataBase } from "@/lib/constants";
+import type { Metadata } from "next";
+
+export const metadata: Metadata = {
+ metadataBase,
+ title: "Account Components",
+ description:
+ "Streamline your Web3 development with our React headless UI components for wallet integration. These unstyled, customizable components handle complex wallet operations while giving you complete control over your dApp's design.",
+};
+
+export default function Page() {
+ return (
+
+
+
+ Streamline your Web3 development with our React headless UI
+ components for wallet integration. These unstyled, customizable
+ components handle complex wallet operations while giving you
+ complete control over your dApp's design.
+ >
+ }
+ docsLink="https://portal.thirdweb.com/react/v5/connecting-wallets/ui-components"
+ heroLink="/headless-ui-header.png"
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/apps/playground-web/src/app/navLinks.ts b/apps/playground-web/src/app/navLinks.ts
index 8b42463b616..d4169e1c54f 100644
--- a/apps/playground-web/src/app/navLinks.ts
+++ b/apps/playground-web/src/app/navLinks.ts
@@ -111,4 +111,18 @@ export const navLinks: SidebarLink[] = [
name: "Blockchain API",
href: "/connect/blockchain-api",
},
+ {
+ name: "Headless UI",
+ expanded: true,
+ links: [
+ {
+ name: "Account",
+ href: "/connect/ui",
+ },
+ {
+ name: "NFT",
+ href: "/connect/ui/nft",
+ },
+ ],
+ },
];
diff --git a/apps/playground-web/src/app/page.tsx b/apps/playground-web/src/app/page.tsx
index 9c9651a6230..1a84adf3d7a 100644
--- a/apps/playground-web/src/app/page.tsx
+++ b/apps/playground-web/src/app/page.tsx
@@ -60,6 +60,12 @@ function WalletsSection() {
description="Performant, and reliable blockchain API"
icon={CodeIcon}
/>
+
);
diff --git a/apps/playground-web/src/components/headless-ui/account-examples.tsx b/apps/playground-web/src/components/headless-ui/account-examples.tsx
new file mode 100644
index 00000000000..f19bf9b9345
--- /dev/null
+++ b/apps/playground-web/src/components/headless-ui/account-examples.tsx
@@ -0,0 +1,509 @@
+"use client";
+
+import { THIRDWEB_CLIENT } from "@/lib/client";
+import { ethereum } from "thirdweb/chains";
+import {
+ AccountAddress,
+ AccountAvatar,
+ AccountBalance,
+ type AccountBalanceInfo,
+ AccountBlobbie,
+ AccountName,
+ AccountProvider,
+} from "thirdweb/react";
+import { shortenAddress } from "thirdweb/utils";
+import { CodeExample } from "../code/code-example";
+
+const vitalikAddress = "0xd8da6bf26964af9d7eed9e03e53415d37aa96045";
+
+/**
+ * Show full wallet address with AccountAddress
+ */
+export function AccountAddressBasic() {
+ return (
+ <>
+
+
+ AccountAddress
+
+
+ Show the wallet address of the account.
+
+
+
+
+
+
+ }
+ code={`import { AccountProvider, AccountAddress } from "thirdweb/react";
+
+function App() {
+ return
+
+
+}`}
+ lang="tsx"
+ />
+ >
+ );
+}
+
+/**
+ * Shorten wallet address with AccountAddress
+ */
+export function AccountAddressFormat() {
+ return (
+ <>
+
+
+ Shorten the wallet address using the formatFn prop.
+
+
+
+
+
+
+ }
+ code={`import { AccountProvider, AccountAddress } from "thirdweb/react";
+import { shortenAddress } from "thirdweb/utils";
+
+function App() {
+ return
+
+
+}`}
+ lang="tsx"
+ />
+ >
+ );
+}
+
+/**
+ * Show the social name of an account
+ */
+export function AccountNameBasic() {
+ return (
+ <>
+
+
+ AccountName
+
+
+ Show the social alias associated with the account
+
+
+
+
+ Loading...} />
+
+ }
+ code={`import { AccountProvider, AccountName } from "thirdweb/react";
+
+function App() {
+ return
+ Loading...} />
+
+}`}
+ lang="tsx"
+ />
+ >
+ );
+}
+
+export function AccountNameCustom() {
+ return (
+ <>
+
+
+ Show account name for a specific social network
+
+
+
+
+ Loading...}
+ />
+
+ }
+ code={`import { AccountProvider, AccountName } from "thirdweb/react";
+
+function App() {
+ return
+ {/* You can choose between "ens", "lens" and "farcaster" */}
+ Loading...} />
+
+}`}
+ lang="tsx"
+ />
+ >
+ );
+}
+
+/**
+ * AccountBalance: Fetch native balance on Ethereum mainnet
+ */
+export function AccountBalanceBasic() {
+ return (
+ <>
+
+
+ AccountBalance
+
+
+ Display the current native balance of the wallet.
+
+
+
+
+ Loading...}
+ />
+
+ }
+ code={`import { AccountProvider, AccountAddress } from "thirdweb/react";
+import { shortenAddress } from "thirdweb/utils";
+
+function App() {
+ return
+ Loading...}
+ />
+
+}`}
+ lang="tsx"
+ />
+ >
+ );
+}
+
+/**
+ * AccountBalance: Fetch USDC balance on mainnet
+ */
+export function AccountBalanceCustomToken() {
+ return (
+ <>
+
+
+ Display the current balance of a custom token
+
+
+
+
+ Loading...}
+ />
+
+ }
+ code={`import { AccountProvider, AccountAddress } from "thirdweb/react";
+import { shortenAddress } from "thirdweb/utils";
+
+const USDC_ETHEREUM = "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48";
+
+function App() {
+ return
+ Loading...}
+ />
+
+}`}
+ lang="tsx"
+ />
+ >
+ );
+}
+
+/**
+ * AccountBalance: Round up the token balance
+ */
+export function AccountBalanceFormat() {
+ return (
+ <>
+
+
+ Round up the wallet balance using the formatFn prop.
+
+
+
+
+
+ `${Math.ceil(props.balance * 1000) / 1000} ${props.symbol}`
+ }
+ loadingComponent={Loading...}
+ />
+
+ }
+ code={`import { AccountProvider, AccountAddress, type AccountBalanceFormatParams } from "thirdweb/react";
+
+function App() {
+ return
+ Loading...}
+ formatFn={(props: AccountBalanceInfo) => \`\${Math.ceil(props.balance * 1000) / 1000} \${props.symbol}\`} />
+
+}`}
+ lang="tsx"
+ />
+ >
+ );
+}
+
+/**
+ * AccountBalance: Show USD balance
+ */
+export function AccountBalanceUSD() {
+ return (
+ <>
+
+
+ Display the USD value of the token balance.
+
+
+
+
+ Loading...}
+ />
+
+ }
+ code={`import { AccountProvider, AccountAddress } from "thirdweb/react";
+
+function App() {
+ return (
+
+ Loading...}
+ />
+
+ )
+}`}
+ lang="tsx"
+ />
+ >
+ );
+}
+
+/**
+ * Show the social name of an account
+ */
+export function AccountAvatarBasic() {
+ return (
+ <>
+
+
+ AccountAvatar
+
+
+ Show the social avatar associated with the account
+
+
+
+
+ Loading...}
+ />
+
+ }
+ code={`import { AccountProvider, AccountAvatar } from "thirdweb/react";
+
+function App() {
+ return
+ Loading...} />
+
+}`}
+ lang="tsx"
+ />
+ >
+ );
+}
+
+export function AccountBlobbieBasic() {
+ return (
+ <>
+
+
+ AccountBlobbie
+
+
+ Show the unique blobbie generated from the wallet address
+
+
+
+
+
+
+ }
+ code={`import { AccountProvider, AccountBlobbie } from "thirdweb/react";
+
+function App() {
+ return
+
+
+}`}
+ lang="tsx"
+ />
+ >
+ );
+}
+
+export function ConnectDetailsButtonClone() {
+ return (
+ <>
+
+
+ Rebuild the Connected-Button
+
+
+ Using these headless components, you can easily build your own complex
+ UI, such as our Connected-button.
+
+
+
+
+
+
+ }
+ code={`import { AccountProvider, AccountAvatar, AccountName, AccountBalance, AccountAddress, AccountBlobbie } from "thirdweb/react";
+
+function App() {
+ return (
+
+
+
+ )
+}`}
+ lang="tsx"
+ />
+ >
+ );
+}
diff --git a/apps/playground-web/src/components/headless-ui/nft-examples.tsx b/apps/playground-web/src/components/headless-ui/nft-examples.tsx
new file mode 100644
index 00000000000..dcb549d2297
--- /dev/null
+++ b/apps/playground-web/src/components/headless-ui/nft-examples.tsx
@@ -0,0 +1,205 @@
+"use client";
+
+import { THIRDWEB_CLIENT } from "@/lib/client";
+import { getContract } from "thirdweb";
+import { ethereum } from "thirdweb/chains";
+import { NFTDescription, NFTMedia, NFTName, NFTProvider } from "thirdweb/react";
+import { CodeExample } from "../code/code-example";
+
+const nftContract = getContract({
+ address: "0xbd3531da5cf5857e7cfaa92426877b022e612cf8",
+ chain: ethereum,
+ client: THIRDWEB_CLIENT,
+});
+
+export function NftMediaBasic() {
+ return (
+ <>
+
+
+ NFTMedia
+
+
+ Show the media of an NFT in a collection.
+
+
+
+
+ Loading...}
+ />
+
+ }
+ code={`import { NFTProvider, NFTMedia } from "thirdweb/react";
+
+function App() {
+ return
+ Loading...} />
+
+}`}
+ lang="tsx"
+ />
+ >
+ );
+}
+
+export function NftMediaOverride() {
+ return (
+ <>
+
+
+ Override the NFT media using the mediaResolver prop.
+
+
+ This prop is very useful when you already have the media src and want
+ to skip the network requests on the client.
+
+
+
+
+
+
+ }
+ code={`import { NFTProvider, NFTMedia } from "thirdweb/react";
+
+function App() {
+ return
+
+
+}`}
+ lang="tsx"
+ />
+ >
+ );
+}
+
+export function NftNameBasic() {
+ return (
+ <>
+
+
+ NFTName
+
+
+ Show the name of an NFT in a collection.
+
+
+
+
+ Loading...} />
+
+ }
+ code={`import { NFTProvider, NFTName } from "thirdweb/react";
+
+function App() {
+ return
+ Loading...} />
+
+}`}
+ lang="tsx"
+ />
+ >
+ );
+}
+
+export function NftDescriptionBasic() {
+ return (
+ <>
+
+
+ NFTDescription
+
+
+ Show the description of an NFT in a collection.
+
+
+
+
+ Loading...}
+ />
+
+ }
+ code={`import { NFTProvider, NFTDescription } from "thirdweb/react";
+
+function App() {
+ return
+ Loading...} />
+
+}`}
+ lang="tsx"
+ />
+ >
+ );
+}
+
+export function NftCardDemo() {
+ return (
+ <>
+
+
+ Build an NFT Card
+
+
+ Using these headless components, you can easily build your own NFT
+ Card
+
+
+
+
+
+
+
+ Loading...}
+ />
+
+
+ }
+ code={`import { NFTProvider, NFTDescription, NFTName, NFTMedia } from "thirdweb/react";
+
+function App() {
+ return (
+
+
+
+
+ Loading...}
+ />
+
+
+ )
+}`}
+ lang="tsx"
+ />
+ >
+ );
+}
diff --git a/packages/thirdweb/src/react/web/ui/prebuilt/Account/avatar.tsx b/packages/thirdweb/src/react/web/ui/prebuilt/Account/avatar.tsx
index ad6444a07df..7aa319cfb12 100644
--- a/packages/thirdweb/src/react/web/ui/prebuilt/Account/avatar.tsx
+++ b/packages/thirdweb/src/react/web/ui/prebuilt/Account/avatar.tsx
@@ -164,7 +164,12 @@ export function AccountAvatar({
}: AccountAvatarProps) {
const { address, client } = useAccountContext();
const avatarQuery = useQuery({
- queryKey: ["account-avatar", address],
+ queryKey: [
+ "account-avatar",
+ address,
+ { socialType },
+ { resolverAddress, resolverChain },
+ ],
queryFn: async (): Promise => {
const [socialData, ensName] = await Promise.all([
getSocialProfiles({ address, client }),
diff --git a/packages/thirdweb/src/react/web/ui/prebuilt/Account/name.tsx b/packages/thirdweb/src/react/web/ui/prebuilt/Account/name.tsx
index 75b634f4861..ff7abd1217c 100644
--- a/packages/thirdweb/src/react/web/ui/prebuilt/Account/name.tsx
+++ b/packages/thirdweb/src/react/web/ui/prebuilt/Account/name.tsx
@@ -57,7 +57,7 @@ export interface AccountNameProps
/**
* Optional `useQuery` params
*/
- queryOptions?: Omit, "queryKey" | "queryFn">;
+ queryOptions?: Omit, "queryFn" | "queryKey">;
}
/**
@@ -138,7 +138,12 @@ export function AccountName({
}: AccountNameProps) {
const { address, client } = useAccountContext();
const nameQuery = useQuery({
- queryKey: ["account-name", address],
+ queryKey: [
+ "account-name",
+ address,
+ { socialType },
+ { resolverAddress, resolverChain },
+ ],
queryFn: async () => {
const [socialData, ensName] = await Promise.all([
getSocialProfiles({ address, client }),