🚚 Installation

npm install remark-tweet-card

or

yarn add remark-tweet-card

🔦 Usage

  1. Import

    Same as any remark plugin. Here’s an example with Astro:

    // astro.config.js
    import { unified } from '@astrojs/markdown-remark';
    import remarkTweetCard from 'remark-tweet-card';
    
    export default defineConfig({
      markdown: {
        processor: unified({
          remarkPlugins: [remarkTweetCard],
        }),
      },
    });
  2. Import global styles

    @import 'remark-tweet-card/style.css';
  3. Use in Markdown

    [$tweet](1654079444816936969)
    
    or
    
    [$tweet](https://x.com/github/status/1654079444816936969)

    It renders as a full tweet card including avatar, body, media, quoted tweets, engagement metrics, and more.


🔧 Configuration

remarkTweetCard({
  // CSS class prefix (default: 'tweet-card')
  prefix: 'tweet-card',

  // API request timeout in milliseconds (default: 10000)
  timeout: 10000,

  // Custom cache instance, must implement get/set/has (default: internal Map)
  cache: new Map(),

  // Custom tweet data fetcher
  fetchTweet: async (id, { timeout, cache }) => {
    /* ... */
  },

  // Custom full tweet card HTML renderer
  renderTweet: (tweet, { prefix, text }) => {
    /* return HTML string */
  },

  // Custom error fallback HTML renderer
  renderError: (url, { prefix, text }) => {
    /* return HTML string */
  },

  // Localized text labels
  text: {
    replies: 'Replies',
    reposts: 'Reposts',
    quotes: 'Quotes',
    likes: 'Likes',
    viewOnX: 'View on X',
    notFound: 'Tweet not available.',
  },
});

Localization

Use the text option to localize the tweet card UI into any language. For example, configure it for Chinese:

remarkTweetCard({
  text: {
    replies: '回复',
    reposts: '转发',
    quotes: '引用',
    likes: '喜欢',
    viewOnX: '在 X 上查看',
    notFound: '推文不可用。',
  },
});

🎨 Custom Styles

A default stylesheet is provided, with all colors controlled via CSS custom properties. Override the following variables to customize the appearance:

VariableDescriptionDefault (light)
--tc-bgCard background#ffffff
--tc-borderBorder color#cfd9de
--tc-textPrimary text color#0f1419
--tc-text-mutedMuted text color#536471
--tc-linkLink color#1d9bf0
--tc-primaryPrimary color (buttons, hover)#1d9bf0
--tc-verifiedVerified badge color#1d9bf0
--tc-font-familyFont familySystem font stack
--tc-overlayVideo play button backgroundrgba(0,0,0,0.65)
--tc-hover-bgHover backgroundrgba(29,155,240,0.1)

Dark Mode

Dark styles are not included by default. Configure them based on your project’s needs:

@media (prefers-color-scheme: dark) {
  :root {
    --tc-bg: #16181c;
    --tc-border: #2f3336;
    --tc-text: #e7e9ea;
    --tc-text-muted: #71767b;
  }
}

or

.dark {
  --tc-bg: #16181c;
  --tc-border: #2f3336;
  --tc-text: #e7e9ea;
  --tc-text-muted: #71767b;
}

😎 Advanced Usage

Custom Data Fetching

If you need to proxy requests or add extra error handling:

import remarkTweetCard from 'remark-tweet-card';
import { fetchTweetData } from 'remark-tweet-card/api';

function myFetcher(id, { timeout, cache }) {
  // Fetch data through your own proxy
  const res = await fetch(`/api/tweet?id=${id}`);
  return res.ok ? res.json() : null;
}

remarkTweetCard({ fetchTweet: myFetcher })

Custom Rendering

Take full control of the HTML output:

remarkTweetCard({
  renderTweet(tweet, { prefix, text }) {
    // Generate HTML with your own template
    return `<blockquote class="${prefix}">${tweet.text}</blockquote>`;
  },
});

Reusing Submodules

The plugin’s internal modules are also exported individually for easy reuse:

import {
  extractTweetId,
  fetchTweetData,
  clearCache,
} from 'remark-tweet-card/api';
import { buildTweetHTML, buildErrorHTML } from 'remark-tweet-card/html';
import { formatCount } from 'remark-tweet-card/utils';

const id = extractTweetId('https://x.com/user/status/123456');
const tweet = await fetchTweetData(id, { timeout: 5000 });
const html = tweet ? buildTweetHTML(tweet, { prefix: 'tweet-card' }) : '';